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:
Nuwan 2026-02-05 10:58:30 +05:30
parent a8ec4e3a88
commit 1eef4d227e
1 changed files with 264 additions and 0 deletions

View File

@ -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);
});
});
});
});