From 29ec90d2cce1e980a4de6072f36408eaf8369a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Tue, 10 Mar 2026 12:44:17 +0800 Subject: [PATCH] test: improve branch coverage with edge cases --- src/lib/auth/permissions.test.ts | 276 +++++++++++++++++++ src/lib/upload.test.ts | 446 +++++++++++++++++++++++++++++++ 2 files changed, 722 insertions(+) create mode 100644 src/lib/auth/permissions.test.ts create mode 100644 src/lib/upload.test.ts diff --git a/src/lib/auth/permissions.test.ts b/src/lib/auth/permissions.test.ts new file mode 100644 index 0000000..1d17a23 --- /dev/null +++ b/src/lib/auth/permissions.test.ts @@ -0,0 +1,276 @@ +import { describe, it, expect } from '@jest/globals'; +import { PERMISSIONS, hasPermission, Role, Resource, Action } from './permissions'; + +describe('permissions', () => { + describe('PERMISSIONS constant', () => { + it('should have admin permissions', () => { + expect(PERMISSIONS.admin).toBeDefined(); + expect(PERMISSIONS.admin.content).toContain('create'); + expect(PERMISSIONS.admin.content).toContain('read'); + expect(PERMISSIONS.admin.content).toContain('update'); + expect(PERMISSIONS.admin.content).toContain('delete'); + expect(PERMISSIONS.admin.content).toContain('publish'); + }); + + it('should have editor permissions', () => { + expect(PERMISSIONS.editor).toBeDefined(); + expect(PERMISSIONS.editor.content).toContain('create'); + expect(PERMISSIONS.editor.content).toContain('read'); + expect(PERMISSIONS.editor.content).toContain('update'); + expect(PERMISSIONS.editor.content).toContain('publish'); + expect(PERMISSIONS.editor.content).not.toContain('delete'); + }); + + it('should have viewer permissions', () => { + expect(PERMISSIONS.viewer).toBeDefined(); + expect(PERMISSIONS.viewer.content).toContain('read'); + expect(PERMISSIONS.viewer.content).not.toContain('create'); + expect(PERMISSIONS.viewer.content).not.toContain('update'); + expect(PERMISSIONS.viewer.content).not.toContain('delete'); + }); + + it('should have config permissions', () => { + expect(PERMISSIONS.admin.config).toContain('read'); + expect(PERMISSIONS.admin.config).toContain('update'); + expect(PERMISSIONS.editor.config).toContain('read'); + expect(PERMISSIONS.editor.config).not.toContain('update'); + }); + + it('should have users permissions', () => { + expect(PERMISSIONS.admin.users).toContain('create'); + expect(PERMISSIONS.admin.users).toContain('read'); + expect(PERMISSIONS.admin.users).toContain('update'); + expect(PERMISSIONS.admin.users).toContain('delete'); + expect(PERMISSIONS.editor.users).toEqual([]); + expect(PERMISSIONS.viewer.users).toEqual([]); + }); + + it('should have logs permissions', () => { + expect(PERMISSIONS.admin.logs).toContain('read'); + expect(PERMISSIONS.editor.logs).toContain('read'); + expect(PERMISSIONS.viewer.logs).toEqual([]); + }); + }); + + describe('hasPermission', () => { + describe('admin role', () => { + it('should allow all content actions', () => { + const role: Role = 'admin'; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'create')).toBe(true); + expect(hasPermission(role, resource, 'read')).toBe(true); + expect(hasPermission(role, resource, 'update')).toBe(true); + expect(hasPermission(role, resource, 'delete')).toBe(true); + expect(hasPermission(role, resource, 'publish')).toBe(true); + }); + + it('should allow config actions', () => { + const role: Role = 'admin'; + const resource: Resource = 'config'; + + expect(hasPermission(role, resource, 'read')).toBe(true); + expect(hasPermission(role, resource, 'update')).toBe(true); + }); + + it('should allow all users actions', () => { + const role: Role = 'admin'; + const resource: Resource = 'users'; + + expect(hasPermission(role, resource, 'create')).toBe(true); + expect(hasPermission(role, resource, 'read')).toBe(true); + expect(hasPermission(role, resource, 'update')).toBe(true); + expect(hasPermission(role, resource, 'delete')).toBe(true); + }); + + it('should allow logs read', () => { + const role: Role = 'admin'; + const resource: Resource = 'logs'; + + expect(hasPermission(role, resource, 'read')).toBe(true); + }); + }); + + describe('editor role', () => { + it('should allow content actions except delete', () => { + const role: Role = 'editor'; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'create')).toBe(true); + expect(hasPermission(role, resource, 'read')).toBe(true); + expect(hasPermission(role, resource, 'update')).toBe(true); + expect(hasPermission(role, resource, 'delete')).toBe(false); + expect(hasPermission(role, resource, 'publish')).toBe(true); + }); + + it('should allow config read only', () => { + const role: Role = 'editor'; + const resource: Resource = 'config'; + + expect(hasPermission(role, resource, 'read')).toBe(true); + expect(hasPermission(role, resource, 'update')).toBe(false); + }); + + it('should not allow users actions', () => { + const role: Role = 'editor'; + const resource: Resource = 'users'; + + expect(hasPermission(role, resource, 'create')).toBe(false); + expect(hasPermission(role, resource, 'read')).toBe(false); + expect(hasPermission(role, resource, 'update')).toBe(false); + expect(hasPermission(role, resource, 'delete')).toBe(false); + }); + + it('should allow logs read', () => { + const role: Role = 'editor'; + const resource: Resource = 'logs'; + + expect(hasPermission(role, resource, 'read')).toBe(true); + }); + }); + + describe('viewer role', () => { + it('should only allow content read', () => { + const role: Role = 'viewer'; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'read')).toBe(true); + expect(hasPermission(role, resource, 'create')).toBe(false); + expect(hasPermission(role, resource, 'update')).toBe(false); + expect(hasPermission(role, resource, 'delete')).toBe(false); + expect(hasPermission(role, resource, 'publish')).toBe(false); + }); + + it('should allow config read only', () => { + const role: Role = 'viewer'; + const resource: Resource = 'config'; + + expect(hasPermission(role, resource, 'read')).toBe(true); + expect(hasPermission(role, resource, 'update')).toBe(false); + }); + + it('should not allow users actions', () => { + const role: Role = 'viewer'; + const resource: Resource = 'users'; + + expect(hasPermission(role, resource, 'create')).toBe(false); + expect(hasPermission(role, resource, 'read')).toBe(false); + expect(hasPermission(role, resource, 'update')).toBe(false); + expect(hasPermission(role, resource, 'delete')).toBe(false); + }); + + it('should not allow logs actions', () => { + const role: Role = 'viewer'; + const resource: Resource = 'logs'; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + }); + + describe('edge cases', () => { + it('should return false for invalid role', () => { + const role = 'invalid' as Role; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should return false for invalid resource', () => { + const role: Role = 'admin'; + const resource = 'invalid' as Resource; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should return false for invalid action', () => { + const role: Role = 'admin'; + const resource: Resource = 'content'; + const action = 'invalid' as Action; + + expect(hasPermission(role, resource, action)).toBe(false); + }); + + it('should handle null role', () => { + const role = null as any; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should handle undefined resource', () => { + const role: Role = 'admin'; + const resource = undefined as any; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should handle empty action string', () => { + const role: Role = 'admin'; + const resource: Resource = 'content'; + const action = '' as Action; + + expect(hasPermission(role, resource, action)).toBe(false); + }); + + it('should handle case sensitivity for roles', () => { + const role = 'ADMIN' as Role; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should handle case sensitivity for resources', () => { + const role: Role = 'admin'; + const resource = 'CONTENT' as Resource; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should handle case sensitivity for actions', () => { + const role: Role = 'admin'; + const resource: Resource = 'content'; + const action = 'READ' as Action; + + expect(hasPermission(role, resource, action)).toBe(false); + }); + + it('should handle undefined role', () => { + const role = undefined as any; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should handle numeric role', () => { + const role = 123 as any; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + + it('should handle object role', () => { + const role = {} as any; + const resource: Resource = 'content'; + + expect(hasPermission(role, resource, 'read')).toBe(false); + }); + }); + }); + + describe('Type Exports', () => { + it('should export Role type', () => { + const role: Role = 'admin'; + expect(role).toBeDefined(); + }); + + it('should export Resource type', () => { + const resource: Resource = 'content'; + expect(resource).toBeDefined(); + }); + + it('should export Action type', () => { + const action: Action = 'read'; + expect(action).toBeDefined(); + }); + }); +}); diff --git a/src/lib/upload.test.ts b/src/lib/upload.test.ts new file mode 100644 index 0000000..d5dc2fd --- /dev/null +++ b/src/lib/upload.test.ts @@ -0,0 +1,446 @@ +import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; +import path from 'path'; +import { writeFile, mkdir, unlink, stat } from 'fs/promises'; +import { existsSync } from 'fs'; + +jest.mock('fs/promises'); +jest.mock('fs'); +jest.mock('nanoid', () => ({ + nanoid: jest.fn(() => 'test-id-12345'), +})); + +const mockedWriteFile = writeFile as jest.MockedFunction; +const mockedMkdir = mkdir as jest.MockedFunction; +const mockedUnlink = unlink as jest.MockedFunction; +const mockedStat = stat as jest.MockedFunction; +const mockedExistsSync = existsSync as jest.MockedFunction; + +describe('Upload Module', () => { + beforeEach(() => { + jest.clearAllMocks(); + process.env.UPLOAD_DIR = './test-uploads'; + }); + + afterEach(() => { + delete process.env.UPLOAD_DIR; + }); + + describe('getFileExtension', () => { + it('should return correct extension for JPEG', async () => { + const { getFileExtension } = await import('./upload'); + expect(getFileExtension('image/jpeg')).toBe('.jpg'); + }); + + it('should return correct extension for PNG', async () => { + const { getFileExtension } = await import('./upload'); + expect(getFileExtension('image/png')).toBe('.png'); + }); + + it('should return correct extension for PDF', async () => { + const { getFileExtension } = await import('./upload'); + expect(getFileExtension('application/pdf')).toBe('.pdf'); + }); + + it('should return empty string for unknown MIME type', async () => { + const { getFileExtension } = await import('./upload'); + expect(getFileExtension('unknown/type')).toBe(''); + }); + }); + + describe('isAllowedType', () => { + it('should return true for allowed image types', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType('image/jpeg', 'image')).toBe(true); + expect(isAllowedType('image/png', 'image')).toBe(true); + expect(isAllowedType('image/gif', 'image')).toBe(true); + }); + + it('should return true for allowed document types', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType('application/pdf', 'document')).toBe(true); + expect(isAllowedType('application/msword', 'document')).toBe(true); + }); + + it('should return false for disallowed types', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType('image/jpeg', 'document')).toBe(false); + expect(isAllowedType('application/pdf', 'image')).toBe(false); + }); + + it('should return false for unknown MIME types', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType('unknown/type', 'image')).toBe(false); + }); + + it('should handle null type', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType(null as any, 'image')).toBe(false); + }); + + it('should handle undefined type', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType(undefined as any, 'image')).toBe(false); + }); + + it('should handle empty string type', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType('', 'image')).toBe(false); + }); + + it('should handle invalid category', async () => { + const { isAllowedType } = await import('./upload'); + expect(isAllowedType('image/jpeg', 'invalid' as any)).toBe(false); + }); + }); + + describe('validateFileSignature', () => { + it('should validate JPEG signature correctly', async () => { + const { validateFileSignature } = await import('./upload'); + const validJpegBuffer = Buffer.from([0xFF, 0xD8, 0xFF, 0x00, 0x00]); + expect(validateFileSignature(validJpegBuffer, 'image/jpeg')).toBe(true); + }); + + it('should validate PNG signature correctly', async () => { + const { validateFileSignature } = await import('./upload'); + const validPngBuffer = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00]); + expect(validateFileSignature(validPngBuffer, 'image/png')).toBe(true); + }); + + it('should validate PDF signature correctly', async () => { + const { validateFileSignature } = await import('./upload'); + const validPdfBuffer = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x00]); + expect(validateFileSignature(validPdfBuffer, 'application/pdf')).toBe(true); + }); + + it('should return false for invalid JPEG signature', async () => { + const { validateFileSignature } = await import('./upload'); + const invalidBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); + expect(validateFileSignature(invalidBuffer, 'image/jpeg')).toBe(false); + }); + + it('should return true for SVG files', async () => { + const { validateFileSignature } = await import('./upload'); + const svgBuffer = Buffer.from(''); + expect(validateFileSignature(svgBuffer, 'image/svg+xml')).toBe(true); + }); + + it('should return true for unknown MIME types', async () => { + const { validateFileSignature } = await import('./upload'); + const buffer = Buffer.from([0x00, 0x00, 0x00]); + expect(validateFileSignature(buffer, 'unknown/type')).toBe(true); + }); + }); + + describe('sanitizeFileName', () => { + it('should remove special characters', async () => { + const { sanitizeFileName } = await import('./upload'); + expect(sanitizeFileName('test<>:"/\\|?*.jpg')).toBe('test_________.jpg'); + }); + + it('should convert to lowercase', async () => { + const { sanitizeFileName } = await import('./upload'); + expect(sanitizeFileName('TestFile.JPG')).toBe('testfile.jpg'); + }); + + it('should replace multiple dots', async () => { + const { sanitizeFileName } = await import('./upload'); + expect(sanitizeFileName('test..file...jpg')).toBe('test.file.jpg'); + }); + + it('should preserve Chinese characters', async () => { + const { sanitizeFileName } = await import('./upload'); + expect(sanitizeFileName('测试文件.jpg')).toBe('测试文件.jpg'); + }); + + it('should preserve underscores and hyphens', async () => { + const { sanitizeFileName } = await import('./upload'); + expect(sanitizeFileName('test_file-name.jpg')).toBe('test_file-name.jpg'); + }); + }); + + describe('isDangerousFile', () => { + it('should detect .exe files as dangerous', async () => { + const { isDangerousFile } = await import('./upload'); + expect(isDangerousFile('malware.exe')).toBe(true); + }); + + it('should detect .bat files as dangerous', async () => { + const { isDangerousFile } = await import('./upload'); + expect(isDangerousFile('script.bat')).toBe(true); + }); + + it('should detect .php files as dangerous', async () => { + const { isDangerousFile } = await import('./upload'); + expect(isDangerousFile('webshell.php')).toBe(true); + }); + + it('should detect .js files as dangerous', async () => { + const { isDangerousFile } = await import('./upload'); + expect(isDangerousFile('script.js')).toBe(true); + }); + + it('should not flag safe files as dangerous', async () => { + const { isDangerousFile } = await import('./upload'); + expect(isDangerousFile('image.jpg')).toBe(false); + expect(isDangerousFile('document.pdf')).toBe(false); + }); + + it('should be case insensitive', async () => { + const { isDangerousFile } = await import('./upload'); + expect(isDangerousFile('MALWARE.EXE')).toBe(true); + expect(isDangerousFile('Script.BAT')).toBe(true); + }); + }); + + describe('getDatePath', () => { + it('should return correct date path format', async () => { + const { getDatePath } = await import('./upload'); + const datePath = getDatePath(); + const regex = /^\d{4}\/\d{2}\/\d{2}$/; + expect(regex.test(datePath)).toBe(true); + }); + + it('should pad month and day with zeros', async () => { + const { getDatePath } = await import('./upload'); + const datePath = getDatePath(); + const parts = datePath.split('/'); + expect(parts[1].length).toBe(2); + expect(parts[2].length).toBe(2); + }); + }); + + describe('uploadFile', () => { + const createMockFile = (overrides: Partial = {}): File => { + return { + name: 'test.jpg', + size: 1024, + type: 'image/jpeg', + arrayBuffer: jest.fn().mockResolvedValue(new ArrayBuffer(1024)), + ...overrides, + } as any; + }; + + it('should upload a valid image file successfully', async () => { + const { uploadFile } = await import('./upload'); + + const validJpegBuffer = Buffer.from([0xFF, 0xD8, 0xFF, 0x00, 0x00]); + const mockFile = createMockFile({ + arrayBuffer: jest.fn().mockResolvedValue(validJpegBuffer), + }); + + mockedExistsSync.mockReturnValue(true); + mockedWriteFile.mockResolvedValue(); + + const result = await uploadFile(mockFile, { type: 'image' }); + + expect(result.id).toBe('test-id-12345'); + expect(result.name).toBe('test.jpg'); + expect(result.type).toBe('image/jpeg'); + expect(result.url).toContain('/uploads/image/'); + expect(result.url).toContain('.jpg'); + }); + + it('should reject files exceeding size limit', async () => { + const { uploadFile } = await import('./upload'); + + const largeFile = createMockFile({ + size: 10 * 1024 * 1024, + }); + + await expect(uploadFile(largeFile, { type: 'image', maxSize: 5 * 1024 * 1024 })) + .rejects.toThrow('文件大小超过限制'); + }); + + it('should reject disallowed file types', async () => { + const { uploadFile } = await import('./upload'); + + const invalidFile = createMockFile({ + type: 'application/exe', + }); + + await expect(uploadFile(invalidFile, { type: 'image' })) + .rejects.toThrow('不支持的文件类型'); + }); + + it('should reject dangerous file extensions', async () => { + const { uploadFile } = await import('./upload'); + + const dangerousFile = createMockFile({ + name: 'malware.exe', + type: 'application/pdf', + }); + + await expect(uploadFile(dangerousFile, { type: 'document' })) + .rejects.toThrow('不允许上传此类型的文件'); + }); + + it('should reject files with mismatched signatures', async () => { + const { uploadFile } = await import('./upload'); + + const fakeJpegBuffer = Buffer.from([0x00, 0x00, 0x00, 0x00]); + const fakeFile = createMockFile({ + type: 'image/jpeg', + arrayBuffer: jest.fn().mockResolvedValue(fakeJpegBuffer), + }); + + await expect(uploadFile(fakeFile, { type: 'image' })) + .rejects.toThrow('文件内容与声明类型不匹配'); + }); + + it('should create upload directory if it does not exist', async () => { + const { uploadFile } = await import('./upload'); + + const validJpegBuffer = Buffer.from([0xFF, 0xD8, 0xFF, 0x00, 0x00]); + const mockFile = createMockFile({ + arrayBuffer: jest.fn().mockResolvedValue(validJpegBuffer), + }); + + mockedExistsSync.mockReturnValue(false); + mockedMkdir.mockResolvedValue(undefined as any); + mockedWriteFile.mockResolvedValue(); + + await uploadFile(mockFile, { type: 'image' }); + + expect(mockedMkdir).toHaveBeenCalled(); + }); + + it('should include userId in upload result', async () => { + const { uploadFile } = await import('./upload'); + + const validJpegBuffer = Buffer.from([0xFF, 0xD8, 0xFF, 0x00, 0x00]); + const mockFile = createMockFile({ + arrayBuffer: jest.fn().mockResolvedValue(validJpegBuffer), + }); + + mockedExistsSync.mockReturnValue(true); + mockedWriteFile.mockResolvedValue(); + + const result = await uploadFile(mockFile, { type: 'image', userId: 'user-123' }); + + expect(result.uploadedBy).toBe('user-123'); + }); + + it('should sanitize file name', async () => { + const { uploadFile } = await import('./upload'); + + const validJpegBuffer = Buffer.from([0xFF, 0xD8, 0xFF, 0x00, 0x00]); + const mockFile = createMockFile({ + name: 'Test<>File.JPG', + arrayBuffer: jest.fn().mockResolvedValue(validJpegBuffer), + }); + + mockedExistsSync.mockReturnValue(true); + mockedWriteFile.mockResolvedValue(); + + const result = await uploadFile(mockFile, { type: 'image' }); + + expect(result.name).toBe('test__file.jpg'); + }); + }); + + describe('deleteFile', () => { + it('should delete existing file successfully', async () => { + const { deleteFile } = await import('./upload'); + + mockedExistsSync.mockReturnValue(true); + mockedUnlink.mockResolvedValue(); + + const result = await deleteFile('/uploads/image/test.jpg'); + + expect(result).toBe(true); + expect(mockedUnlink).toHaveBeenCalled(); + }); + + it('should return false for non-existent file', async () => { + const { deleteFile } = await import('./upload'); + + mockedExistsSync.mockReturnValue(false); + + const result = await deleteFile('/uploads/image/nonexistent.jpg'); + + expect(result).toBe(false); + expect(mockedUnlink).not.toHaveBeenCalled(); + }); + + it('should return false on deletion error', async () => { + const { deleteFile } = await import('./upload'); + + mockedExistsSync.mockReturnValue(true); + mockedUnlink.mockRejectedValue(new Error('Permission denied')); + + const result = await deleteFile('/uploads/image/test.jpg'); + + expect(result).toBe(false); + }); + }); + + describe('getFileInfo', () => { + it('should return file information for existing file', async () => { + const { getFileInfo } = await import('./upload'); + + const mockStats = { + size: 1024, + birthtime: new Date('2024-01-01'), + mtime: new Date('2024-01-02'), + }; + + mockedStat.mockResolvedValue(mockStats as any); + + const result = await getFileInfo('/path/to/file.jpg'); + + expect(result).toEqual({ + size: 1024, + createdAt: mockStats.birthtime, + modifiedAt: mockStats.mtime, + }); + }); + + it('should return null for non-existent file', async () => { + const { getFileInfo } = await import('./upload'); + + mockedStat.mockRejectedValue(new Error('File not found')); + + const result = await getFileInfo('/path/to/nonexistent.jpg'); + + expect(result).toBeNull(); + }); + }); + + describe('Security Tests', () => { + it('should prevent path traversal attacks', async () => { + const { sanitizeFileName } = await import('./upload'); + const maliciousName = '../../../etc/passwd'; + const sanitized = sanitizeFileName(maliciousName); + + expect(sanitized).not.toContain('/'); + expect(sanitized).not.toContain('..'); + }); + + it('should prevent double extension attacks', async () => { + const { sanitizeFileName } = await import('./upload'); + const maliciousName = 'image.jpg.php'; + const sanitized = sanitizeFileName(maliciousName); + + expect(sanitized).toBe('image.jpg.php'); + }); + + it('should validate file signatures to prevent MIME type spoofing', async () => { + const { validateFileSignature } = await import('./upload'); + + const fakeJpegBuffer = Buffer.from([0x25, 0x50, 0x44, 0x46]); + expect(validateFileSignature(fakeJpegBuffer, 'image/jpeg')).toBe(false); + + const realJpegBuffer = Buffer.from([0xFF, 0xD8, 0xFF]); + expect(validateFileSignature(realJpegBuffer, 'image/jpeg')).toBe(true); + }); + + it('should reject all dangerous file extensions', async () => { + const { isDangerousFile } = await import('./upload'); + const dangerousExtensions = ['.exe', '.bat', '.cmd', '.sh', '.php', '.jsp', '.asp', '.aspx', '.js']; + + dangerousExtensions.forEach(ext => { + expect(isDangerousFile(`malware${ext}`)).toBe(true); + }); + }); + }); +});