sound.test.tsβ’5.89 kB
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { spawn } from 'child_process';
import { EventEmitter } from 'events';
// Mock child_process
vi.mock('child_process');
const mockSpawn = vi.mocked(spawn);
// Mock fs/promises for file tests
vi.mock('node:fs/promises', () => ({
stat: vi.fn(),
}));
import { stat } from 'node:fs/promises';
const mockStat = vi.mocked(stat);
// We need to import these functions - let's create a testable version
// Since the main file doesn't export functions, we'll test the core logic
const ALLOWED_SYSTEM_SOUNDS = new Set([
'Basso', 'Blow', 'Bottle', 'Frog', 'Funk', 'Glass', 'Hero', 'Morse',
'Ping', 'Pop', 'Purr', 'Sosumi', 'Submarine', 'Tink'
]);
const MAX_TTS_TEXT_LENGTH = 1000;
// Mock process for testing
class MockChildProcess extends EventEmitter {
stdout = new EventEmitter();
stderr = new EventEmitter();
once(event: string, callback: (...args: unknown[]) => void): this {
super.once(event, callback);
return this;
}
}
describe('Sound System', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('System Sound Validation', () => {
it('should allow valid system sounds', () => {
expect(ALLOWED_SYSTEM_SOUNDS.has('Glass')).toBe(true);
expect(ALLOWED_SYSTEM_SOUNDS.has('Ping')).toBe(true);
expect(ALLOWED_SYSTEM_SOUNDS.has('Sosumi')).toBe(true);
});
it('should reject invalid system sounds', () => {
expect(ALLOWED_SYSTEM_SOUNDS.has('InvalidSound')).toBe(false);
expect(ALLOWED_SYSTEM_SOUNDS.has('')).toBe(false);
});
});
describe('TTS Text Validation', () => {
it('should allow text within length limit', () => {
const shortText = 'Hello world';
expect(shortText.length <= MAX_TTS_TEXT_LENGTH).toBe(true);
});
it('should reject text exceeding length limit', () => {
const longText = 'a'.repeat(MAX_TTS_TEXT_LENGTH + 1);
expect(longText.length > MAX_TTS_TEXT_LENGTH).toBe(true);
});
});
describe('Sound Playback Simulation', () => {
it('should spawn afplay for system sounds', () => {
const mockProcess = new MockChildProcess();
mockSpawn.mockReturnValue(mockProcess as unknown as ReturnType<typeof spawn>);
// Simulate system sound playback
const soundName = 'Glass';
const expectedArgs = [`/System/Library/Sounds/${soundName}.aiff`];
// This would be called in the actual implementation
spawn('afplay', expectedArgs);
expect(mockSpawn).toHaveBeenCalledWith('afplay', expectedArgs);
});
it('should spawn say command for TTS', () => {
const mockProcess = new MockChildProcess();
mockSpawn.mockReturnValue(mockProcess as unknown as ReturnType<typeof spawn>);
const text = 'Hello world';
const voice = 'Samantha';
// This would be called in the actual implementation
spawn('say', ['-v', voice, text]);
expect(mockSpawn).toHaveBeenCalledWith('say', ['-v', voice, text]);
});
it('should handle successful sound playback', () => {
const mockProcess = new MockChildProcess();
mockSpawn.mockReturnValue(mockProcess as unknown as ReturnType<typeof spawn>);
spawn('afplay', ['/System/Library/Sounds/Glass.aiff']);
// Simulate successful completion
setTimeout(() => {
mockProcess.emit('close', 0);
}, 0);
expect(mockSpawn).toHaveBeenCalled();
});
it('should handle failed sound playback', () => {
const mockProcess = new MockChildProcess();
mockSpawn.mockReturnValue(mockProcess as unknown as ReturnType<typeof spawn>);
spawn('afplay', ['/System/Library/Sounds/Glass.aiff']);
// Simulate failure
setTimeout(() => {
mockProcess.emit('close', 1);
}, 0);
expect(mockSpawn).toHaveBeenCalled();
});
});
describe('File Path Validation', () => {
it('should validate absolute paths', async () => {
const absolutePath = '/absolute/path/to/file.mp3';
mockStat.mockResolvedValue({ isFile: () => true } as Awaited<ReturnType<typeof stat>>);
await stat(absolutePath);
expect(mockStat).toHaveBeenCalledWith(absolutePath);
});
it('should handle file stat errors', async () => {
const invalidPath = '/invalid/path.mp3';
mockStat.mockRejectedValue(new Error('File not found'));
try {
await stat(invalidPath);
} catch (error) {
expect(error).toBeInstanceOf(Error);
}
});
});
describe('Input Validation', () => {
it('should validate sound type parameter', () => {
const validTypes = ['system', 'tts', 'file'];
const invalidType = 'invalid';
expect(validTypes.includes('system')).toBe(true);
expect(validTypes.includes('tts')).toBe(true);
expect(validTypes.includes('file')).toBe(true);
expect(validTypes.includes(invalidType)).toBe(false);
});
it('should require name for system type', () => {
const systemSoundWithName = { type: 'system', name: 'Glass' };
const systemSoundWithoutName = { type: 'system' };
expect(systemSoundWithName.name).toBeDefined();
expect((systemSoundWithoutName as Record<string, unknown>).name).toBeUndefined();
});
it('should require text for tts type', () => {
const ttsWithText = { type: 'tts', text: 'Hello' };
const ttsWithoutText = { type: 'tts' };
expect(ttsWithText.text).toBeDefined();
expect((ttsWithoutText as Record<string, unknown>).text).toBeUndefined();
});
it('should require path for file type', () => {
const fileWithPath = { type: 'file', path: '/path/to/file.mp3' };
const fileWithoutPath = { type: 'file' };
expect(fileWithPath.path).toBeDefined();
expect((fileWithoutPath as Record<string, unknown>).path).toBeUndefined();
});
});
});