test(13-01): add failing tests for attachment validation service
RED phase - TDD cycle - 15+ test cases covering all 5 functions - validateFileSize: size limits and edge cases - validateFileType: extension whitelist and case handling - getAttachmentType: audio vs notation detection - validateFile: combined validation with warnings - formatFileSize: human-readable formatting - Constants validation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a8ec4e3a88
commit
1eef4d227e
|
|
@ -0,0 +1,264 @@
|
|||
import {
|
||||
validateFileSize,
|
||||
validateFileType,
|
||||
getAttachmentType,
|
||||
validateFile,
|
||||
formatFileSize,
|
||||
MAX_FILE_SIZE,
|
||||
ALLOWED_EXTENSIONS
|
||||
} from '../attachmentValidation';
|
||||
|
||||
describe('attachmentValidation', () => {
|
||||
describe('validateFileSize', () => {
|
||||
test('accepts file under limit', () => {
|
||||
const file = new File(['content'], 'test.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 5 * 1024 * 1024 }); // 5 MB
|
||||
const result = validateFileSize(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('rejects file over limit', () => {
|
||||
const file = new File(['content'], 'large.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 15 * 1024 * 1024 }); // 15 MB
|
||||
const result = validateFileSize(file);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('10 MB');
|
||||
});
|
||||
|
||||
test('accepts file exactly at limit', () => {
|
||||
const file = new File(['content'], 'exact.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 10 * 1024 * 1024 }); // Exactly 10 MB
|
||||
const result = validateFileSize(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('rejects file exactly over limit by 1 byte', () => {
|
||||
const file = new File(['content'], 'oversize.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 10 * 1024 * 1024 + 1 }); // 10 MB + 1 byte
|
||||
const result = validateFileSize(file);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('10 MB');
|
||||
});
|
||||
|
||||
test('accepts custom max size', () => {
|
||||
const file = new File(['content'], 'test.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 8 * 1024 * 1024 }); // 8 MB
|
||||
const result = validateFileSize(file, 5 * 1024 * 1024); // 5 MB limit
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('5 MB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateFileType', () => {
|
||||
test('accepts pdf file', () => {
|
||||
const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
|
||||
const result = validateFileType(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('accepts mp3 file', () => {
|
||||
const file = new File(['content'], 'music.mp3', { type: 'audio/mpeg' });
|
||||
const result = validateFileType(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('accepts wav file', () => {
|
||||
const file = new File(['content'], 'track.wav', { type: 'audio/wav' });
|
||||
const result = validateFileType(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('accepts png file', () => {
|
||||
const file = new File(['content'], 'image.png', { type: 'image/png' });
|
||||
const result = validateFileType(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('rejects exe file', () => {
|
||||
const file = new File(['content'], 'virus.exe', { type: 'application/x-msdownload' });
|
||||
const result = validateFileType(file);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('not allowed');
|
||||
expect(result.error).toContain('.pdf');
|
||||
});
|
||||
|
||||
test('rejects zip file', () => {
|
||||
const file = new File(['content'], 'archive.zip', { type: 'application/zip' });
|
||||
const result = validateFileType(file);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('not allowed');
|
||||
});
|
||||
|
||||
test('accepts uppercase extension', () => {
|
||||
const file = new File(['content'], 'DOCUMENT.PDF', { type: 'application/pdf' });
|
||||
const result = validateFileType(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('accepts mixed case extension', () => {
|
||||
const file = new File(['content'], 'Document.Pdf', { type: 'application/pdf' });
|
||||
const result = validateFileType(file);
|
||||
expect(result).toEqual({ valid: true, error: null });
|
||||
});
|
||||
|
||||
test('accepts all allowed extensions', () => {
|
||||
const extensions = ['.pdf', '.xml', '.mxl', '.txt', '.png', '.jpg', '.jpeg', '.gif', '.mp3', '.wav'];
|
||||
extensions.forEach(ext => {
|
||||
const file = new File(['content'], `test${ext}`, { type: 'application/octet-stream' });
|
||||
const result = validateFileType(file);
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAttachmentType', () => {
|
||||
test('identifies mp3 as audio', () => {
|
||||
expect(getAttachmentType('song.mp3')).toBe('audio');
|
||||
});
|
||||
|
||||
test('identifies wav as audio', () => {
|
||||
expect(getAttachmentType('track.wav')).toBe('audio');
|
||||
});
|
||||
|
||||
test('identifies flac as audio', () => {
|
||||
expect(getAttachmentType('audio.flac')).toBe('audio');
|
||||
});
|
||||
|
||||
test('identifies ogg as audio', () => {
|
||||
expect(getAttachmentType('music.ogg')).toBe('audio');
|
||||
});
|
||||
|
||||
test('identifies pdf as notation', () => {
|
||||
expect(getAttachmentType('sheet.pdf')).toBe('notation');
|
||||
});
|
||||
|
||||
test('identifies xml as notation', () => {
|
||||
expect(getAttachmentType('score.xml')).toBe('notation');
|
||||
});
|
||||
|
||||
test('identifies png as notation', () => {
|
||||
expect(getAttachmentType('image.png')).toBe('notation');
|
||||
});
|
||||
|
||||
test('identifies txt as notation', () => {
|
||||
expect(getAttachmentType('notes.txt')).toBe('notation');
|
||||
});
|
||||
|
||||
test('handles uppercase extensions', () => {
|
||||
expect(getAttachmentType('SONG.MP3')).toBe('audio');
|
||||
expect(getAttachmentType('SHEET.PDF')).toBe('notation');
|
||||
});
|
||||
|
||||
test('handles multiple dots in filename', () => {
|
||||
expect(getAttachmentType('my.song.name.mp3')).toBe('audio');
|
||||
expect(getAttachmentType('document.v2.pdf')).toBe('notation');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateFile', () => {
|
||||
test('accepts valid pdf file', () => {
|
||||
const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 5 * 1024 * 1024 });
|
||||
const result = validateFile(file);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
test('rejects file over size limit', () => {
|
||||
const file = new File(['content'], 'large.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 15 * 1024 * 1024 });
|
||||
const result = validateFile(file);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('10 MB');
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
test('rejects invalid file type', () => {
|
||||
const file = new File(['content'], 'virus.exe', { type: 'application/x-msdownload' });
|
||||
Object.defineProperty(file, 'size', { value: 1024 });
|
||||
const result = validateFile(file);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('not allowed');
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
test('warns about mp3 files (not backend supported)', () => {
|
||||
const file = new File(['content'], 'music.mp3', { type: 'audio/mpeg' });
|
||||
Object.defineProperty(file, 'size', { value: 5 * 1024 * 1024 });
|
||||
const result = validateFile(file);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.warnings).toHaveLength(1);
|
||||
expect(result.warnings[0]).toContain('.mp3');
|
||||
expect(result.warnings[0]).toContain('may not be supported');
|
||||
});
|
||||
|
||||
test('does not warn about wav files (backend supported)', () => {
|
||||
const file = new File(['content'], 'track.wav', { type: 'audio/wav' });
|
||||
Object.defineProperty(file, 'size', { value: 5 * 1024 * 1024 });
|
||||
const result = validateFile(file);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
test('does not warn about pdf files (backend supported)', () => {
|
||||
const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
|
||||
Object.defineProperty(file, 'size', { value: 5 * 1024 * 1024 });
|
||||
const result = validateFile(file);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
test('stops at first error (does not check type if size fails)', () => {
|
||||
const file = new File(['content'], 'large.exe', { type: 'application/x-msdownload' });
|
||||
Object.defineProperty(file, 'size', { value: 15 * 1024 * 1024 });
|
||||
const result = validateFile(file);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.error).toContain('10 MB'); // Size error, not type error
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatFileSize', () => {
|
||||
test('formats bytes', () => {
|
||||
expect(formatFileSize(0)).toBe('0 B');
|
||||
expect(formatFileSize(500)).toBe('500 B');
|
||||
expect(formatFileSize(1023)).toBe('1023 B');
|
||||
});
|
||||
|
||||
test('formats kilobytes', () => {
|
||||
expect(formatFileSize(1024)).toBe('1.0 KB');
|
||||
expect(formatFileSize(2048)).toBe('2.0 KB');
|
||||
expect(formatFileSize(1536)).toBe('1.5 KB');
|
||||
expect(formatFileSize(10240)).toBe('10.0 KB');
|
||||
});
|
||||
|
||||
test('formats megabytes', () => {
|
||||
expect(formatFileSize(1024 * 1024)).toBe('1.0 MB');
|
||||
expect(formatFileSize(5 * 1024 * 1024)).toBe('5.0 MB');
|
||||
expect(formatFileSize(5242880)).toBe('5.0 MB');
|
||||
expect(formatFileSize(10 * 1024 * 1024)).toBe('10.0 MB');
|
||||
});
|
||||
|
||||
test('rounds to one decimal place', () => {
|
||||
expect(formatFileSize(1536)).toBe('1.5 KB');
|
||||
expect(formatFileSize(2.5 * 1024 * 1024)).toBe('2.5 MB');
|
||||
expect(formatFileSize(3.75 * 1024 * 1024)).toBe('3.8 MB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('constants', () => {
|
||||
test('MAX_FILE_SIZE is 10 MB', () => {
|
||||
expect(MAX_FILE_SIZE).toBe(10 * 1024 * 1024);
|
||||
});
|
||||
|
||||
test('ALLOWED_EXTENSIONS includes all required types', () => {
|
||||
const requiredExtensions = ['.pdf', '.xml', '.mxl', '.txt', '.png', '.jpg', '.jpeg', '.gif', '.mp3', '.wav'];
|
||||
requiredExtensions.forEach(ext => {
|
||||
expect(ALLOWED_EXTENSIONS).toContain(ext);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue