import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
import { AudioInspector } from '../lib/inspector.js';
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe('Audio Inspector Edge Cases and Error Conditions', () => {
let inspector;
let testDir;
beforeEach(async () => {
inspector = new AudioInspector();
testDir = path.join(__dirname, 'edge-cases');
await fs.mkdir(testDir, { recursive: true });
});
afterEach(async () => {
await fs.rmdir(testDir, { recursive: true }).catch(() => {});
});
describe('File System Edge Cases', () => {
test('should handle zero-byte files', async () => {
const emptyFile = path.join(testDir, 'empty.mp3');
await fs.writeFile(emptyFile, '');
const result = await inspector.analyzeFile(emptyFile);
expect(result).toBeDefined();
expect(result.file.size).toBe(0);
if (result.error) {
expect(result.message).toBeDefined();
}
});
test('should handle files with only metadata headers', async () => {
const headerOnlyFile = path.join(testDir, 'header-only.mp3');
// Create file with just ID3 header but no audio data
await fs.writeFile(headerOnlyFile, 'ID3\x03\x00\x00\x00\x00\x00\x00\x00');
const result = await inspector.analyzeFile(headerOnlyFile);
expect(result).toBeDefined();
expect(result.file.size).toBeGreaterThan(0);
});
test('should handle extremely long file paths', async () => {
// Create a very long path (but within OS limits)
let longPath = testDir;
const maxSegments = 10; // Reasonable limit for testing
for (let i = 0; i < maxSegments; i++) {
const segment = `very-long-directory-name-segment-${i}`.repeat(3);
longPath = path.join(longPath, segment);
await fs.mkdir(longPath, { recursive: true });
}
const longFileName = 'extremely-long-audio-file-name-for-testing-edge-cases.mp3';
const longFilePath = path.join(longPath, longFileName);
await fs.writeFile(longFilePath, 'long path test content');
const result = await inspector.analyzeFile(longFilePath);
expect(result).toBeDefined();
expect(result.file.path).toContain(longFileName);
});
test('should handle files with special characters in names', async () => {
const specialNames = [
'file@with#special$chars%.mp3',
'file[with]brackets.mp3',
'file{with}braces.mp3',
'file(with)parentheses.mp3'
];
for (const fileName of specialNames) {
try {
const filePath = path.join(testDir, fileName);
await fs.writeFile(filePath, 'special chars test');
const result = await inspector.analyzeFile(filePath);
expect(result).toBeDefined();
expect(result.file.name).toBe(fileName);
} catch (error) {
// Some special characters might not be supported on all filesystems
console.warn(`Special character test skipped for ${fileName}: ${error.message}`);
}
}
});
test('should handle symbolic links appropriately', async () => {
const originalFile = path.join(testDir, 'original.mp3');
const symlinkFile = path.join(testDir, 'symlink.mp3');
await fs.writeFile(originalFile, 'original file content');
try {
await fs.symlink(originalFile, symlinkFile);
const originalResult = await inspector.analyzeFile(originalFile);
const symlinkResult = await inspector.analyzeFile(symlinkFile);
expect(originalResult).toBeDefined();
expect(symlinkResult).toBeDefined();
// Both should have the same content size
expect(symlinkResult.file.size).toBe(originalResult.file.size);
} catch (error) {
// Symbolic links might not be supported on all platforms
console.warn(`Symbolic link test skipped: ${error.message}`);
}
});
});
describe('Malformed Audio Data', () => {
test('should handle truncated audio files', async () => {
const truncatedFile = path.join(testDir, 'truncated.mp3');
// Create file that starts like MP3 but is truncated
await fs.writeFile(truncatedFile, 'ID3\x03\x00\x00\x00\x00\x00\x10\x00TRUNC');
const result = await inspector.analyzeFile(truncatedFile);
expect(result).toBeDefined();
// Should handle gracefully, either with error or fallback
if (result.error) {
expect(result.message).toContain('ID3');
} else {
expect(result.source).toBeDefined();
}
});
test('should handle files with wrong extensions', async () => {
const wrongExtensions = [
{ name: 'text-as-audio.mp3', content: 'This is actually a text file' },
{ name: 'binary-as-audio.wav', content: '\x00\x01\x02\x03\x04\x05\x06\x07' },
{ name: 'html-as-audio.flac', content: '<html><body>Not audio</body></html>' }
];
for (const testCase of wrongExtensions) {
const filePath = path.join(testDir, testCase.name);
await fs.writeFile(filePath, testCase.content);
const result = await inspector.analyzeFile(filePath);
expect(result).toBeDefined();
// Should attempt analysis but likely fail or fallback
expect(result.file.name).toBe(testCase.name);
}
});
test('should handle corrupted metadata tags', async () => {
const corruptedFile = path.join(testDir, 'corrupted-metadata.mp3');
// Create file with corrupted ID3 tag structure
const corruptedData = 'ID3\x04\x00\x00\x00\x00\x00\xFF\xFF' +
'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' +
'corrupted tag data';
await fs.writeFile(corruptedFile, corruptedData);
const result = await inspector.analyzeFile(corruptedFile);
expect(result).toBeDefined();
// Should handle corrupted metadata gracefully
if (!result.error) {
expect(result.tags).toBeDefined();
}
});
test('should handle extremely large metadata blocks', async () => {
const largeMetadataFile = path.join(testDir, 'large-metadata.mp3');
// Create file with very large metadata block
const largeMetadata = 'A'.repeat(1024 * 1024); // 1MB of metadata
const fileContent = 'ID3\x03\x00\x00\x00\x00\x00\x00' + largeMetadata;
await fs.writeFile(largeMetadataFile, fileContent);
const result = await inspector.analyzeFile(largeMetadataFile);
expect(result).toBeDefined();
expect(result.file.size).toBeGreaterThan(1024 * 1024);
});
});
describe('Unusual Audio Characteristics', () => {
test('should handle files with extreme sample rates', async () => {
// Test with mock metadata for extreme cases
const extremeCases = [
{ sampleRate: 8000, description: 'Very low sample rate' },
{ sampleRate: 192000, description: 'Very high sample rate' },
{ sampleRate: 0, description: 'Zero sample rate' },
{ sampleRate: -1, description: 'Negative sample rate' }
];
for (const testCase of extremeCases) {
const mockMetadata = {
format: {
sampleRate: testCase.sampleRate,
numberOfChannels: 2,
duration: 10
}
};
const formatInfo = inspector.extractFormatInfo(mockMetadata);
expect(formatInfo.sampleRate).toBe(testCase.sampleRate);
// Game analysis should handle extreme values
const gameAnalysis = await inspector.analyzeForGameAudio(
{ format: formatInfo, file: { size: 100000 } },
'extreme-test.mp3'
);
expect(gameAnalysis).toBeDefined();
}
});
test('should handle files with unusual channel configurations', async () => {
const channelConfigs = [
{ channels: 0, description: 'No channels' },
{ channels: 1, description: 'Mono' },
{ channels: 8, description: 'Surround 7.1' },
{ channels: 32, description: 'Many channels' }
];
for (const config of channelConfigs) {
const mockMetadata = {
format: {
numberOfChannels: config.channels,
sampleRate: 44100,
duration: 60
}
};
const formatInfo = inspector.extractFormatInfo(mockMetadata);
expect(formatInfo.channels).toBe(config.channels);
const gameAnalysis = await inspector.analyzeForGameAudio(
{ format: formatInfo, file: { size: 1000000 } },
'channel-test.mp3'
);
expect(gameAnalysis.platformOptimizations).toBeDefined();
expect(gameAnalysis.platformOptimizations.console).toBeDefined();
}
});
test('should handle files with extreme durations', async () => {
const durationCases = [
{ duration: 0, description: 'Zero duration' },
{ duration: 0.01, description: 'Very short' },
{ duration: 86400, description: '24 hours' },
{ duration: -1, description: 'Negative duration' }
];
for (const testCase of durationCases) {
const mockBasicInfo = {
format: {
duration: testCase.duration,
sampleRate: 44100,
channels: 2
},
file: { size: 1000000 }
};
const gameAnalysis = await inspector.analyzeForGameAudio(mockBasicInfo, 'duration-test.mp3');
expect(gameAnalysis).toBeDefined();
expect(typeof gameAnalysis.suitableForLoop).toBe('boolean');
}
});
});
describe('System Resource Limits', () => {
test('should handle insufficient disk space scenarios', async () => {
// This test simulates behavior when disk space is low
// In real scenarios, this would involve actual disk space constraints
const testFile = path.join(testDir, 'disk-space-test.mp3');
await fs.writeFile(testFile, 'disk space test content');
// Simulate the analysis continuing normally
const result = await inspector.analyzeFile(testFile);
expect(result).toBeDefined();
// Should complete analysis even with disk constraints
expect(result.file.name).toBe('disk-space-test.mp3');
});
test('should handle concurrent access to the same file', async () => {
const sharedFile = path.join(testDir, 'shared-file.mp3');
await fs.writeFile(sharedFile, 'shared file content for concurrent access');
// Attempt multiple concurrent analyses of the same file
const concurrentPromises = Array.from({ length: 5 }, () =>
inspector.analyzeFile(sharedFile)
);
const results = await Promise.all(concurrentPromises);
// All analyses should complete successfully
expect(results.length).toBe(5);
results.forEach(result => {
expect(result).toBeDefined();
expect(result.file.name).toBe('shared-file.mp3');
});
});
});
describe('Network and Remote File Scenarios', () => {
test('should handle UNC paths appropriately', async () => {
// Test with UNC-style paths (Windows network paths)
if (process.platform === 'win32') {
const uncStylePath = '\\\\server\\share\\audio\\test.mp3';
// Since we can't create actual UNC paths in tests, test path handling
const result = await inspector.analyzeFile(uncStylePath);
// Should handle the path format, even if file doesn't exist
expect(result).toBeDefined();
expect(result.file.path).toContain('test.mp3');
}
});
});
describe('Memory and Performance Edge Cases', () => {
test('should handle rapid successive analyses', async () => {
const rapidFile = path.join(testDir, 'rapid-test.mp3');
await fs.writeFile(rapidFile, 'content for rapid testing');
const rapidCount = 20;
const startTime = Date.now();
// Perform rapid successive analyses
const results = [];
for (let i = 0; i < rapidCount; i++) {
const result = await inspector.analyzeFile(rapidFile);
results.push(result);
}
const endTime = Date.now();
const totalTime = endTime - startTime;
expect(results.length).toBe(rapidCount);
expect(totalTime).toBeLessThan(10000); // Should complete within 10 seconds
// All results should be consistent
const firstResult = results[0];
results.forEach(result => {
expect(result.file.size).toBe(firstResult.file.size);
});
});
test('should handle analysis interruption gracefully', async () => {
const interruptFile = path.join(testDir, 'interrupt-test.mp3');
await fs.writeFile(interruptFile, 'interrupt test content');
// Start analysis and immediately start another
const promise1 = inspector.analyzeFile(interruptFile);
const promise2 = inspector.analyzeFile(interruptFile);
const [result1, result2] = await Promise.all([promise1, promise2]);
expect(result1).toBeDefined();
expect(result2).toBeDefined();
// Both should complete successfully
expect(result1.file.name).toBe('interrupt-test.mp3');
expect(result2.file.name).toBe('interrupt-test.mp3');
});
});
describe('Data Validation Edge Cases', () => {
test('should handle null and undefined metadata gracefully', async () => {
const nullCases = [
null,
undefined,
{},
{ format: null },
{ common: null },
{ format: {}, common: undefined }
];
for (const testCase of nullCases) {
expect(() => inspector.extractFormatInfo(testCase)).not.toThrow();
expect(() => inspector.extractTags(testCase)).not.toThrow();
const formatInfo = inspector.extractFormatInfo(testCase);
const tags = inspector.extractTags(testCase);
expect(formatInfo).toBeDefined();
expect(tags).toBeDefined();
}
});
test('should handle circular references in metadata', async () => {
// Create object with circular reference
const circularMetadata = {
format: {
container: 'MP3',
codec: 'mp3'
}
};
circularMetadata.format.self = circularMetadata.format;
// Should handle without throwing
expect(() => inspector.extractFormatInfo(circularMetadata)).not.toThrow();
const formatInfo = inspector.extractFormatInfo(circularMetadata);
expect(formatInfo.container).toBe('MP3');
});
test('should handle extremely nested metadata structures', async () => {
// Create deeply nested metadata
let deepMetadata = { format: {} };
let current = deepMetadata.format;
for (let i = 0; i < 100; i++) {
current.nested = { level: i };
current = current.nested;
}
deepMetadata.format.container = 'DEEP';
deepMetadata.format.codec = 'nested';
const formatInfo = inspector.extractFormatInfo(deepMetadata);
expect(formatInfo.container).toBe('DEEP');
expect(formatInfo.codec).toBe('nested');
});
});
describe('Format-Specific Edge Cases', () => {
test('should handle unknown audio formats gracefully', async () => {
const unknownFile = path.join(testDir, 'unknown.xyz');
await fs.writeFile(unknownFile, 'unknown format content');
const result = await inspector.analyzeFile(unknownFile);
expect(result).toBeDefined();
// Should attempt analysis even for unknown extensions
expect(result.file.name).toBe('unknown.xyz');
});
test('should handle mixed format signatures', async () => {
const mixedFile = path.join(testDir, 'mixed-format.mp3');
// Create file with conflicting format signatures
const mixedContent = 'ID3\x03\x00\x00RIFF\x24\x08\x00\x00WAVEfLaC';
await fs.writeFile(mixedFile, mixedContent);
const result = await inspector.analyzeFile(mixedFile);
expect(result).toBeDefined();
// Should handle conflicting signatures gracefully
expect(result.file.name).toBe('mixed-format.mp3');
});
});
});