@oletizi/audio-tools
Version:
Monorepo for hardware sampler utilities and format parsers
422 lines (354 loc) • 11.5 kB
text/typescript
import { describe, it, expect } from 'vitest';
import {
validateUnifiedConfig,
validateBackupSource,
validateExportConfig,
} from '@/validator.js';
import type { AudioToolsConfig, BackupSource, ExportConfig, BackupConfig } from '@/types.js';
describe('validator', () => {
describe('validateBackupSource', () => {
it('should validate valid remote source', () => {
const source: BackupSource = {
name: 'pi-scsi2',
type: 'remote',
source: 'pi-scsi2.local:~/images/',
device: 'images',
enabled: true,
};
expect(() => validateBackupSource(source)).not.toThrow();
});
it('should validate valid local source', () => {
const source: BackupSource = {
name: 's5k-gotek',
type: 'local',
source: '/Volumes/GOTEK',
device: 'floppy',
sampler: 's5k-studio',
enabled: false,
};
expect(() => validateBackupSource(source)).not.toThrow();
});
it('should throw error for missing name', () => {
const source = {
type: 'remote',
source: 'host:~/images/',
device: 'images',
enabled: true,
} as any;
expect(() => validateBackupSource(source)).toThrow('Backup source name cannot be empty');
});
it('should throw error for empty name', () => {
const source: BackupSource = {
name: '',
type: 'remote',
source: 'host:~/images/',
device: 'images',
enabled: true,
};
expect(() => validateBackupSource(source)).toThrow('Backup source name cannot be empty');
});
it('should throw error for invalid type', () => {
const source = {
name: 'test',
type: 'invalid',
source: 'path',
device: 'device',
enabled: true,
} as any;
expect(() => validateBackupSource(source)).toThrow('Invalid source type');
});
it('should throw error for missing source path', () => {
const source = {
name: 'test',
type: 'remote',
device: 'images',
enabled: true,
} as any;
expect(() => validateBackupSource(source)).toThrow('Source path cannot be empty');
});
it('should throw error for empty source path', () => {
const source: BackupSource = {
name: 'test',
type: 'remote',
source: '',
device: 'images',
enabled: true,
};
expect(() => validateBackupSource(source)).toThrow('Source path cannot be empty');
});
it('should throw error for missing device', () => {
const source = {
name: 'test',
type: 'remote',
source: 'host:~/images/',
enabled: true,
} as any;
expect(() => validateBackupSource(source)).toThrow('Device name cannot be empty');
});
it('should throw error for empty device', () => {
const source: BackupSource = {
name: 'test',
type: 'remote',
source: 'host:~/images/',
device: '',
enabled: true,
};
expect(() => validateBackupSource(source)).toThrow('Device name cannot be empty');
});
it('should validate local source with sampler name', () => {
const source: BackupSource = {
name: 'local-test',
type: 'local',
source: '/Volumes/USB',
device: 'usb',
sampler: 'my-sampler',
enabled: true,
};
expect(() => validateBackupSource(source)).not.toThrow();
});
});
describe('validateExportConfig', () => {
it('should validate valid export config', () => {
const config: ExportConfig = {
outputRoot: '~/.audiotools/sampler-export/extracted',
formats: ['sfz', 'decentsampler'],
skipUnchanged: true,
enabledSources: ['source-1', 'source-2'],
};
expect(() => validateExportConfig(config)).not.toThrow();
});
it('should throw error for missing outputRoot', () => {
const config = {
formats: ['sfz'],
skipUnchanged: true,
enabledSources: [],
} as any;
expect(() => validateExportConfig(config)).toThrow('Export outputRoot cannot be empty');
});
it('should throw error for empty outputRoot', () => {
const config: ExportConfig = {
outputRoot: '',
formats: ['sfz'],
skipUnchanged: true,
enabledSources: [],
};
expect(() => validateExportConfig(config)).toThrow('outputRoot cannot be empty');
});
it('should throw error for missing formats', () => {
const config = {
outputRoot: '/path',
skipUnchanged: true,
enabledSources: [],
} as any;
expect(() => validateExportConfig(config)).toThrow('formats must be an array');
});
it('should throw error for empty formats array', () => {
const config: ExportConfig = {
outputRoot: '/path',
formats: [],
skipUnchanged: true,
enabledSources: [],
};
expect(() => validateExportConfig(config)).toThrow('Export formats array cannot be empty');
});
it('should throw error for invalid format', () => {
const config = {
outputRoot: '/path',
formats: ['invalid'],
skipUnchanged: true,
enabledSources: [],
} as any;
expect(() => validateExportConfig(config)).toThrow('Invalid export formats');
});
it('should validate single format', () => {
const config: ExportConfig = {
outputRoot: '/path',
formats: ['sfz'],
skipUnchanged: true,
enabledSources: [],
};
expect(() => validateExportConfig(config)).not.toThrow();
});
it('should throw error for missing enabledSources', () => {
const config = {
outputRoot: '/path',
formats: ['sfz'],
skipUnchanged: true,
} as any;
expect(() => validateExportConfig(config)).toThrow('Export enabledSources must be an array');
});
it('should validate config with only backup section', () => {
const config: AudioToolsConfig = {
version: '1.0',
backup: {
backupRoot: '/path',
sources: [],
},
};
expect(() => validateUnifiedConfig(config)).not.toThrow();
});
it('should validate config with only export section', () => {
const config: AudioToolsConfig = {
version: '1.0',
export: {
outputRoot: '/path',
formats: ['sfz'],
skipUnchanged: true,
enabledSources: [],
},
};
expect(() => validateUnifiedConfig(config)).not.toThrow();
});
it('should validate config with custom tool sections', () => {
const config: AudioToolsConfig = {
version: '1.0',
'custom-tool': {
setting1: 'value1',
setting2: 42,
},
};
expect(() => validateUnifiedConfig(config)).not.toThrow();
});
it('should throw error for invalid backup config', () => {
const config = {
version: '1.0',
backup: {
sources: [],
},
} as any;
expect(() => validateUnifiedConfig(config)).toThrow('Backup backupRoot cannot be empty');
});
it('should throw error for invalid export config', () => {
const config = {
version: '1.0',
export: {
outputRoot: '/path',
formats: [],
skipUnchanged: true,
enabledSources: [],
},
} as any;
expect(() => validateUnifiedConfig(config)).toThrow('Export config: Export formats array cannot be empty');
});
});
describe('UUID Field Validation', () => {
it('should accept valid UUID fields', () => {
const source: BackupSource = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
volumeUUID: '12345678-1234-1234-1234-123456789012',
volumeLabel: 'SDCARD',
volumeSerial: '12345678-02',
lastSeen: '2025-10-09T14:30:00.000Z',
registeredAt: '2025-10-09T14:30:00.000Z'
};
expect(() => validateBackupSource(source)).not.toThrow();
});
it('should accept sources without UUID fields', () => {
const source: BackupSource = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true
};
expect(() => validateBackupSource(source)).not.toThrow();
});
it('should reject invalid lastSeen timestamp', () => {
const source: BackupSource = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
lastSeen: 'not-a-timestamp'
};
expect(() => validateBackupSource(source))
.toThrow('Invalid lastSeen timestamp');
});
it('should reject invalid registeredAt timestamp', () => {
const source: BackupSource = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
registeredAt: '2025-10-09' // Not ISO 8601 with time
};
expect(() => validateBackupSource(source))
.toThrow('Invalid registeredAt timestamp');
});
it('should reject non-string volumeUUID', () => {
const source = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
volumeUUID: 12345 // Wrong type
} as any;
expect(() => validateBackupSource(source))
.toThrow('volumeUUID must be a string');
});
it('should reject non-string volumeLabel', () => {
const source = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
volumeLabel: 12345 // Wrong type
} as any;
expect(() => validateBackupSource(source))
.toThrow('volumeLabel must be a string');
});
it('should reject non-string volumeSerial', () => {
const source = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
volumeSerial: 12345 // Wrong type
} as any;
expect(() => validateBackupSource(source))
.toThrow('volumeSerial must be a string');
});
it('should accept partial UUID fields', () => {
const source: BackupSource = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
volumeLabel: 'SDCARD',
lastSeen: '2025-10-09T14:30:00.000Z'
};
expect(() => validateBackupSource(source)).not.toThrow();
});
it('should accept volumeSerial without volumeUUID', () => {
const source: BackupSource = {
name: 's5k-card',
type: 'local',
source: '/Volumes/SDCARD',
device: 'sd-card',
sampler: 's5000',
enabled: true,
volumeSerial: '12345678-02',
registeredAt: '2025-10-09T14:00:00.000Z'
};
expect(() => validateBackupSource(source)).not.toThrow();
});
});
});