"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const testing_1 = require("@nestjs/testing");
const typeorm_1 = require("@nestjs/typeorm");
const common_1 = require("@nestjs/common");
const storage_service_1 = require("../services/storage.service");
const file_entity_1 = require("../entities/file.entity");
const pending_upload_entity_1 = require("../entities/pending-upload.entity");
const storage_usage_entity_1 = require("../entities/storage-usage.entity");
const s3_provider_1 = require("../providers/s3.provider");
describe('StorageService', () => {
    let service;
    let fileRepo;
    let pendingUploadRepo;
    let usageRepo;
    let s3Provider;
    const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
    const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
    const mockFile = {
        id: 'file-001',
        tenant_id: mockTenantId,
        uploaded_by: mockUserId,
        filename: 'test_image.jpg',
        original_name: 'test image.jpg',
        mime_type: 'image/jpeg',
        size_bytes: 1024000,
        bucket: 'template-saas',
        path: `${mockTenantId}/files/file-001/test_image.jpg`,
        provider: file_entity_1.StorageProvider.S3,
        folder: 'files',
        status: file_entity_1.FileStatus.READY,
        visibility: file_entity_1.FileVisibility.PRIVATE,
        created_at: new Date(),
    };
    const mockPendingUpload = {
        id: 'upload-001',
        tenant_id: mockTenantId,
        user_id: mockUserId,
        filename: 'test_image.jpg',
        original_name: 'test image.jpg',
        mime_type: 'image/jpeg',
        size_bytes: 1024000,
        folder: 'files',
        bucket: 'template-saas',
        path: `${mockTenantId}/files/upload-001/test_image.jpg`,
        provider: file_entity_1.StorageProvider.S3,
        status: 'pending',
        expires_at: new Date(Date.now() + 3600000),
    };
    const mockUsage = {
        id: 'usage-001',
        tenant_id: mockTenantId,
        total_files: 10,
        total_bytes: 10485760,
        max_bytes: 1073741824,
        max_file_size: 52428800,
    };
    beforeEach(async () => {
        const mockFileRepo = {
            create: jest.fn(),
            save: jest.fn(),
            findOne: jest.fn(),
            find: jest.fn(),
            findAndCount: jest.fn(),
            createQueryBuilder: jest.fn(),
        };
        const mockPendingUploadRepo = {
            create: jest.fn(),
            save: jest.fn(),
            findOne: jest.fn(),
            update: jest.fn(),
        };
        const mockUsageRepo = {
            findOne: jest.fn(),
            create: jest.fn(),
            save: jest.fn(),
        };
        const mockS3Provider = {
            isConfigured: jest.fn(),
            getBucket: jest.fn(),
            getProvider: jest.fn(),
            generatePath: jest.fn(),
            getUploadUrl: jest.fn(),
            getDownloadUrl: jest.fn(),
            headObject: jest.fn(),
            deleteObject: jest.fn(),
        };
        const module = await testing_1.Test.createTestingModule({
            providers: [
                storage_service_1.StorageService,
                { provide: (0, typeorm_1.getRepositoryToken)(file_entity_1.FileEntity), useValue: mockFileRepo },
                { provide: (0, typeorm_1.getRepositoryToken)(pending_upload_entity_1.PendingUploadEntity), useValue: mockPendingUploadRepo },
                { provide: (0, typeorm_1.getRepositoryToken)(storage_usage_entity_1.StorageUsageEntity), useValue: mockUsageRepo },
                { provide: s3_provider_1.S3Provider, useValue: mockS3Provider },
            ],
        }).compile();
        service = module.get(storage_service_1.StorageService);
        fileRepo = module.get((0, typeorm_1.getRepositoryToken)(file_entity_1.FileEntity));
        pendingUploadRepo = module.get((0, typeorm_1.getRepositoryToken)(pending_upload_entity_1.PendingUploadEntity));
        usageRepo = module.get((0, typeorm_1.getRepositoryToken)(storage_usage_entity_1.StorageUsageEntity));
        s3Provider = module.get(s3_provider_1.S3Provider);
    });
    afterEach(() => {
        jest.clearAllMocks();
    });
    describe('getUploadUrl', () => {
        const uploadDto = {
            filename: 'test image.jpg',
            mimeType: 'image/jpeg',
            sizeBytes: 1024000,
        };
        it('should return presigned upload URL', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            s3Provider.getBucket.mockReturnValue('template-saas');
            s3Provider.getProvider.mockReturnValue(file_entity_1.StorageProvider.S3);
            s3Provider.generatePath.mockReturnValue(`${mockTenantId}/files/xxx/test_image.jpg`);
            s3Provider.getUploadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/presigned-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            usageRepo.findOne.mockResolvedValue(null);
            pendingUploadRepo.create.mockReturnValue(mockPendingUpload);
            pendingUploadRepo.save.mockResolvedValue(mockPendingUpload);
            const result = await service.getUploadUrl(mockTenantId, mockUserId, uploadDto);
            expect(result).toHaveProperty('uploadId');
            expect(result).toHaveProperty('url');
            expect(result).toHaveProperty('expiresAt');
            expect(s3Provider.getUploadUrl).toHaveBeenCalled();
        });
        it('should throw when storage not configured', async () => {
            s3Provider.isConfigured.mockReturnValue(false);
            await expect(service.getUploadUrl(mockTenantId, mockUserId, uploadDto)).rejects.toThrow(common_1.BadRequestException);
        });
        it('should throw for disallowed MIME type', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            await expect(service.getUploadUrl(mockTenantId, mockUserId, {
                ...uploadDto,
                mimeType: 'application/x-executable',
            })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should throw for blocked file extension', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            await expect(service.getUploadUrl(mockTenantId, mockUserId, {
                ...uploadDto,
                filename: 'malicious.exe',
                mimeType: 'image/jpeg',
            })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should throw when file exceeds max size', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            usageRepo.findOne.mockResolvedValue({
                ...mockUsage,
                max_file_size: 1000,
            });
            await expect(service.getUploadUrl(mockTenantId, mockUserId, uploadDto)).rejects.toThrow(common_1.ForbiddenException);
        });
        it('should throw when storage limit exceeded', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            usageRepo.findOne.mockResolvedValue({
                ...mockUsage,
                total_bytes: 1073741800,
                max_bytes: 1073741824,
            });
            await expect(service.getUploadUrl(mockTenantId, mockUserId, {
                ...uploadDto,
                sizeBytes: 100000000,
            })).rejects.toThrow(common_1.ForbiddenException);
        });
    });
    describe('confirmUpload', () => {
        it('should confirm upload successfully', async () => {
            pendingUploadRepo.findOne.mockResolvedValue(mockPendingUpload);
            s3Provider.headObject.mockResolvedValue({ contentLength: 1024000, contentType: 'image/png' });
            fileRepo.create.mockReturnValue(mockFile);
            fileRepo.save.mockResolvedValue(mockFile);
            pendingUploadRepo.update.mockResolvedValue({ affected: 1 });
            const result = await service.confirmUpload(mockTenantId, mockUserId, {
                uploadId: 'upload-001',
            });
            expect(result).toHaveProperty('id');
            expect(result).toHaveProperty('filename');
            expect(fileRepo.save).toHaveBeenCalled();
        });
        it('should throw when upload not found', async () => {
            pendingUploadRepo.findOne.mockResolvedValue(null);
            await expect(service.confirmUpload(mockTenantId, mockUserId, { uploadId: 'invalid' })).rejects.toThrow(common_1.NotFoundException);
        });
        it('should throw when upload expired', async () => {
            pendingUploadRepo.findOne.mockResolvedValue({
                ...mockPendingUpload,
                expires_at: new Date(Date.now() - 3600000),
            });
            pendingUploadRepo.update.mockResolvedValue({ affected: 1 });
            await expect(service.confirmUpload(mockTenantId, mockUserId, { uploadId: 'upload-001' })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should throw when file not found in S3', async () => {
            pendingUploadRepo.findOne.mockResolvedValue(mockPendingUpload);
            s3Provider.headObject.mockResolvedValue(null);
            pendingUploadRepo.update.mockResolvedValue({ affected: 1 });
            await expect(service.confirmUpload(mockTenantId, mockUserId, { uploadId: 'upload-001' })).rejects.toThrow(common_1.BadRequestException);
        });
    });
    describe('listFiles', () => {
        it('should return paginated files', async () => {
            fileRepo.findAndCount.mockResolvedValue([[mockFile], 1]);
            const result = await service.listFiles(mockTenantId, { page: 1, limit: 20 });
            expect(result.data).toHaveLength(1);
            expect(result.total).toBe(1);
            expect(result.page).toBe(1);
            expect(result.totalPages).toBe(1);
        });
        it('should filter by folder', async () => {
            fileRepo.findAndCount.mockResolvedValue([[mockFile], 1]);
            await service.listFiles(mockTenantId, { folder: 'images' });
            expect(fileRepo.findAndCount).toHaveBeenCalledWith(expect.objectContaining({
                where: expect.objectContaining({ folder: 'images' }),
            }));
        });
        it('should use default pagination', async () => {
            fileRepo.findAndCount.mockResolvedValue([[], 0]);
            const result = await service.listFiles(mockTenantId, {});
            expect(result.page).toBe(1);
            expect(result.limit).toBe(20);
        });
    });
    describe('getFile', () => {
        it('should return file by id', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            const result = await service.getFile(mockTenantId, 'file-001');
            expect(result).toHaveProperty('id', 'file-001');
            expect(result).toHaveProperty('filename');
        });
        it('should throw when file not found', async () => {
            fileRepo.findOne.mockResolvedValue(null);
            await expect(service.getFile(mockTenantId, 'invalid')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('getDownloadUrl', () => {
        it('should return presigned download URL', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            s3Provider.getDownloadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/download-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            const result = await service.getDownloadUrl(mockTenantId, 'file-001');
            expect(result).toHaveProperty('url');
            expect(result).toHaveProperty('expiresAt');
        });
        it('should throw when file not found', async () => {
            fileRepo.findOne.mockResolvedValue(null);
            await expect(service.getDownloadUrl(mockTenantId, 'invalid')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('updateFile', () => {
        it('should update file folder', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockResolvedValue({ ...mockFile, folder: 'images' });
            const result = await service.updateFile(mockTenantId, 'file-001', {
                folder: 'images',
            });
            expect(result.folder).toBe('images');
        });
        it('should update file visibility', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockResolvedValue({
                ...mockFile,
                visibility: file_entity_1.FileVisibility.PUBLIC,
            });
            const result = await service.updateFile(mockTenantId, 'file-001', {
                visibility: file_entity_1.FileVisibility.PUBLIC,
            });
            expect(result.visibility).toBe(file_entity_1.FileVisibility.PUBLIC);
        });
        it('should throw when file not found', async () => {
            fileRepo.findOne.mockResolvedValue(null);
            await expect(service.updateFile(mockTenantId, 'invalid', { folder: 'new' })).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('deleteFile', () => {
        it('should soft delete file', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockResolvedValue({
                ...mockFile,
                status: file_entity_1.FileStatus.DELETED,
                deleted_at: new Date(),
            });
            s3Provider.deleteObject.mockResolvedValue(undefined);
            await service.deleteFile(mockTenantId, 'file-001');
            expect(fileRepo.save).toHaveBeenCalledWith(expect.objectContaining({
                status: file_entity_1.FileStatus.DELETED,
            }));
        });
        it('should handle S3 deletion failure gracefully', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockResolvedValue({
                ...mockFile,
                status: file_entity_1.FileStatus.DELETED,
            });
            s3Provider.deleteObject.mockRejectedValue(new Error('S3 error'));
            await expect(service.deleteFile(mockTenantId, 'file-001')).resolves.not.toThrow();
        });
        it('should throw when file not found', async () => {
            fileRepo.findOne.mockResolvedValue(null);
            await expect(service.deleteFile(mockTenantId, 'invalid')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('getUsage', () => {
        it('should return storage usage', async () => {
            usageRepo.findOne.mockResolvedValue(mockUsage);
            const mockQueryBuilder = {
                select: jest.fn().mockReturnThis(),
                addSelect: jest.fn().mockReturnThis(),
                where: jest.fn().mockReturnThis(),
                andWhere: jest.fn().mockReturnThis(),
                groupBy: jest.fn().mockReturnThis(),
                getRawMany: jest.fn().mockResolvedValue([
                    { folder: 'files', count: '5' },
                    { folder: 'images', count: '5' },
                ]),
            };
            fileRepo.createQueryBuilder.mockReturnValue(mockQueryBuilder);
            const result = await service.getUsage(mockTenantId);
            expect(result).toHaveProperty('totalFiles', 10);
            expect(result).toHaveProperty('totalBytes');
            expect(result).toHaveProperty('filesByFolder');
        });
        it('should create default usage if not exists', async () => {
            usageRepo.findOne.mockResolvedValue(null);
            usageRepo.create.mockReturnValue({
                tenant_id: mockTenantId,
                total_files: 0,
                total_bytes: 0,
            });
            usageRepo.save.mockResolvedValue({
                tenant_id: mockTenantId,
                total_files: 0,
                total_bytes: 0,
            });
            const mockQueryBuilder = {
                select: jest.fn().mockReturnThis(),
                addSelect: jest.fn().mockReturnThis(),
                where: jest.fn().mockReturnThis(),
                andWhere: jest.fn().mockReturnThis(),
                groupBy: jest.fn().mockReturnThis(),
                getRawMany: jest.fn().mockResolvedValue([]),
            };
            fileRepo.createQueryBuilder.mockReturnValue(mockQueryBuilder);
            const result = await service.getUsage(mockTenantId);
            expect(result.totalFiles).toBe(0);
            expect(usageRepo.create).toHaveBeenCalled();
        });
        it('should calculate usage percentage correctly', async () => {
            usageRepo.findOne.mockResolvedValue({
                ...mockUsage,
                total_bytes: 536870912,
                max_bytes: 1073741824,
            });
            const mockQueryBuilder = {
                select: jest.fn().mockReturnThis(),
                addSelect: jest.fn().mockReturnThis(),
                where: jest.fn().mockReturnThis(),
                andWhere: jest.fn().mockReturnThis(),
                groupBy: jest.fn().mockReturnThis(),
                getRawMany: jest.fn().mockResolvedValue([]),
            };
            fileRepo.createQueryBuilder.mockReturnValue(mockQueryBuilder);
            const result = await service.getUsage(mockTenantId);
            expect(result.usagePercent).toBe(50);
        });
        it('should return zero usage percent when no max_bytes set', async () => {
            usageRepo.findOne.mockResolvedValue({
                ...mockUsage,
                max_bytes: null,
            });
            const mockQueryBuilder = {
                select: jest.fn().mockReturnThis(),
                addSelect: jest.fn().mockReturnThis(),
                where: jest.fn().mockReturnThis(),
                andWhere: jest.fn().mockReturnThis(),
                groupBy: jest.fn().mockReturnThis(),
                getRawMany: jest.fn().mockResolvedValue([]),
            };
            fileRepo.createQueryBuilder.mockReturnValue(mockQueryBuilder);
            const result = await service.getUsage(mockTenantId);
            expect(result.usagePercent).toBe(0);
            expect(result.maxBytes).toBeNull();
        });
        it('should aggregate files by folder correctly', async () => {
            usageRepo.findOne.mockResolvedValue(mockUsage);
            const mockQueryBuilder = {
                select: jest.fn().mockReturnThis(),
                addSelect: jest.fn().mockReturnThis(),
                where: jest.fn().mockReturnThis(),
                andWhere: jest.fn().mockReturnThis(),
                groupBy: jest.fn().mockReturnThis(),
                getRawMany: jest.fn().mockResolvedValue([
                    { folder: 'documents', count: '10' },
                    { folder: 'images', count: '25' },
                    { folder: 'videos', count: '5' },
                ]),
            };
            fileRepo.createQueryBuilder.mockReturnValue(mockQueryBuilder);
            const result = await service.getUsage(mockTenantId);
            expect(result.filesByFolder).toEqual({
                documents: 10,
                images: 25,
                videos: 5,
            });
        });
    });
    describe('getUploadUrl - additional cases', () => {
        const baseUploadDto = {
            filename: 'test.jpg',
            mimeType: 'image/jpeg',
            sizeBytes: 1024000,
        };
        it('should accept PNG images', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            s3Provider.getBucket.mockReturnValue('template-saas');
            s3Provider.getProvider.mockReturnValue(file_entity_1.StorageProvider.S3);
            s3Provider.generatePath.mockReturnValue(`${mockTenantId}/files/xxx/test.png`);
            s3Provider.getUploadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/presigned-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            usageRepo.findOne.mockResolvedValue(null);
            pendingUploadRepo.create.mockReturnValue(mockPendingUpload);
            pendingUploadRepo.save.mockResolvedValue(mockPendingUpload);
            const result = await service.getUploadUrl(mockTenantId, mockUserId, {
                ...baseUploadDto,
                filename: 'test.png',
                mimeType: 'image/png',
            });
            expect(result).toHaveProperty('url');
        });
        it('should accept PDF documents', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            s3Provider.getBucket.mockReturnValue('template-saas');
            s3Provider.getProvider.mockReturnValue(file_entity_1.StorageProvider.S3);
            s3Provider.generatePath.mockReturnValue(`${mockTenantId}/files/xxx/document.pdf`);
            s3Provider.getUploadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/presigned-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            usageRepo.findOne.mockResolvedValue(null);
            pendingUploadRepo.create.mockReturnValue(mockPendingUpload);
            pendingUploadRepo.save.mockResolvedValue(mockPendingUpload);
            const result = await service.getUploadUrl(mockTenantId, mockUserId, {
                filename: 'document.pdf',
                mimeType: 'application/pdf',
                sizeBytes: 2048000,
            });
            expect(result).toHaveProperty('url');
        });
        it('should accept CSV files', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            s3Provider.getBucket.mockReturnValue('template-saas');
            s3Provider.getProvider.mockReturnValue(file_entity_1.StorageProvider.S3);
            s3Provider.generatePath.mockReturnValue(`${mockTenantId}/files/xxx/data.csv`);
            s3Provider.getUploadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/presigned-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            usageRepo.findOne.mockResolvedValue(null);
            pendingUploadRepo.create.mockReturnValue(mockPendingUpload);
            pendingUploadRepo.save.mockResolvedValue(mockPendingUpload);
            const result = await service.getUploadUrl(mockTenantId, mockUserId, {
                filename: 'data.csv',
                mimeType: 'text/csv',
                sizeBytes: 512000,
            });
            expect(result).toHaveProperty('url');
        });
        it('should reject .bat files', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            await expect(service.getUploadUrl(mockTenantId, mockUserId, {
                ...baseUploadDto,
                filename: 'script.bat',
            })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should reject .sh files', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            await expect(service.getUploadUrl(mockTenantId, mockUserId, {
                ...baseUploadDto,
                filename: 'script.sh',
            })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should reject .php files', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            await expect(service.getUploadUrl(mockTenantId, mockUserId, {
                ...baseUploadDto,
                filename: 'backdoor.php',
            })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should reject .js files', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            await expect(service.getUploadUrl(mockTenantId, mockUserId, {
                ...baseUploadDto,
                filename: 'malicious.js',
            })).rejects.toThrow(common_1.BadRequestException);
        });
        it('should use custom folder when provided', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            s3Provider.getBucket.mockReturnValue('template-saas');
            s3Provider.getProvider.mockReturnValue(file_entity_1.StorageProvider.S3);
            s3Provider.generatePath.mockReturnValue(`${mockTenantId}/images/xxx/test.jpg`);
            s3Provider.getUploadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/presigned-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            usageRepo.findOne.mockResolvedValue(null);
            pendingUploadRepo.create.mockReturnValue({
                ...mockPendingUpload,
                folder: 'images',
            });
            pendingUploadRepo.save.mockResolvedValue({
                ...mockPendingUpload,
                folder: 'images',
            });
            await service.getUploadUrl(mockTenantId, mockUserId, {
                ...baseUploadDto,
                folder: 'images',
            });
            expect(s3Provider.generatePath).toHaveBeenCalledWith(mockTenantId, 'images', expect.any(String), baseUploadDto.filename);
        });
        it('should allow upload when tenant has no usage record', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            s3Provider.getBucket.mockReturnValue('template-saas');
            s3Provider.getProvider.mockReturnValue(file_entity_1.StorageProvider.S3);
            s3Provider.generatePath.mockReturnValue(`${mockTenantId}/files/xxx/test.jpg`);
            s3Provider.getUploadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/presigned-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            usageRepo.findOne.mockResolvedValue(null);
            pendingUploadRepo.create.mockReturnValue(mockPendingUpload);
            pendingUploadRepo.save.mockResolvedValue(mockPendingUpload);
            const result = await service.getUploadUrl(mockTenantId, mockUserId, baseUploadDto);
            expect(result).toHaveProperty('url');
        });
        it('should include maxSize in response', async () => {
            s3Provider.isConfigured.mockReturnValue(true);
            s3Provider.getBucket.mockReturnValue('template-saas');
            s3Provider.getProvider.mockReturnValue(file_entity_1.StorageProvider.S3);
            s3Provider.generatePath.mockReturnValue(`${mockTenantId}/files/xxx/test.jpg`);
            s3Provider.getUploadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/presigned-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            usageRepo.findOne.mockResolvedValue(null);
            pendingUploadRepo.create.mockReturnValue(mockPendingUpload);
            pendingUploadRepo.save.mockResolvedValue(mockPendingUpload);
            const result = await service.getUploadUrl(mockTenantId, mockUserId, baseUploadDto);
            expect(result.maxSize).toBe(baseUploadDto.sizeBytes);
        });
    });
    describe('confirmUpload - additional cases', () => {
        it('should include metadata in file when provided', async () => {
            pendingUploadRepo.findOne.mockResolvedValue(mockPendingUpload);
            s3Provider.headObject.mockResolvedValue({ contentLength: 1024000, contentType: 'image/jpeg' });
            fileRepo.create.mockReturnValue({
                ...mockFile,
                metadata: { description: 'Test file', tags: ['test'] },
            });
            fileRepo.save.mockResolvedValue({
                ...mockFile,
                metadata: { description: 'Test file', tags: ['test'] },
            });
            pendingUploadRepo.update.mockResolvedValue({ affected: 1 });
            const result = await service.confirmUpload(mockTenantId, mockUserId, {
                uploadId: 'upload-001',
                metadata: { description: 'Test file', tags: ['test'] },
            });
            expect(result.metadata).toEqual({ description: 'Test file', tags: ['test'] });
        });
        it('should mark pending upload as failed when file not in S3', async () => {
            pendingUploadRepo.findOne.mockResolvedValue(mockPendingUpload);
            s3Provider.headObject.mockResolvedValue(null);
            pendingUploadRepo.update.mockResolvedValue({ affected: 1 });
            await expect(service.confirmUpload(mockTenantId, mockUserId, { uploadId: 'upload-001' })).rejects.toThrow(common_1.BadRequestException);
            expect(pendingUploadRepo.update).toHaveBeenCalledWith(mockPendingUpload.id, { status: 'failed' });
        });
        it('should use actual file size from S3', async () => {
            const actualSize = 2048000;
            pendingUploadRepo.findOne.mockResolvedValue(mockPendingUpload);
            s3Provider.headObject.mockResolvedValue({ contentLength: actualSize, contentType: 'image/jpeg' });
            fileRepo.create.mockReturnValue({
                ...mockFile,
                size_bytes: actualSize,
            });
            fileRepo.save.mockResolvedValue({
                ...mockFile,
                size_bytes: actualSize,
            });
            pendingUploadRepo.update.mockResolvedValue({ affected: 1 });
            const result = await service.confirmUpload(mockTenantId, mockUserId, {
                uploadId: 'upload-001',
            });
            expect(result.sizeBytes).toBe(actualSize);
        });
    });
    describe('listFiles - additional cases', () => {
        it('should filter by MIME type prefix', async () => {
            fileRepo.findAndCount.mockResolvedValue([[mockFile], 1]);
            await service.listFiles(mockTenantId, { mimeType: 'image' });
            expect(fileRepo.findAndCount).toHaveBeenCalled();
        });
        it('should search by original name', async () => {
            fileRepo.findAndCount.mockResolvedValue([[mockFile], 1]);
            await service.listFiles(mockTenantId, { search: 'test' });
            expect(fileRepo.findAndCount).toHaveBeenCalled();
        });
        it('should calculate total pages correctly', async () => {
            const files = Array(25).fill(mockFile);
            fileRepo.findAndCount.mockResolvedValue([files.slice(0, 10), 25]);
            const result = await service.listFiles(mockTenantId, { page: 1, limit: 10 });
            expect(result.totalPages).toBe(3);
        });
        it('should handle empty results', async () => {
            fileRepo.findAndCount.mockResolvedValue([[], 0]);
            const result = await service.listFiles(mockTenantId, {});
            expect(result.data).toHaveLength(0);
            expect(result.total).toBe(0);
            expect(result.totalPages).toBe(0);
        });
        it('should apply pagination offset correctly', async () => {
            fileRepo.findAndCount.mockResolvedValue([[mockFile], 50]);
            await service.listFiles(mockTenantId, { page: 3, limit: 10 });
            expect(fileRepo.findAndCount).toHaveBeenCalledWith(expect.objectContaining({
                skip: 20,
                take: 10,
            }));
        });
    });
    describe('deleteFile - additional cases', () => {
        it('should set deleted_at timestamp', async () => {
            const savedFile = { ...mockFile };
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockImplementation((file) => {
                Object.assign(savedFile, file);
                return Promise.resolve(savedFile);
            });
            s3Provider.deleteObject.mockResolvedValue(undefined);
            await service.deleteFile(mockTenantId, 'file-001');
            expect(savedFile.deleted_at).toBeInstanceOf(Date);
        });
        it('should call S3 deleteObject with correct path', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockResolvedValue({
                ...mockFile,
                status: file_entity_1.FileStatus.DELETED,
                deleted_at: new Date(),
            });
            s3Provider.deleteObject.mockResolvedValue(undefined);
            await service.deleteFile(mockTenantId, 'file-001');
            expect(s3Provider.deleteObject).toHaveBeenCalledWith(mockFile.path);
        });
    });
    describe('getDownloadUrl - additional cases', () => {
        it('should call S3 provider with file path', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            s3Provider.getDownloadUrl.mockResolvedValue({
                url: 'https://s3.amazonaws.com/download-url',
                expiresAt: new Date(Date.now() + 3600000),
            });
            await service.getDownloadUrl(mockTenantId, 'file-001');
            expect(s3Provider.getDownloadUrl).toHaveBeenCalledWith(mockFile.path);
        });
        it('should not return download URL for deleted files', async () => {
            fileRepo.findOne.mockResolvedValue(null);
            await expect(service.getDownloadUrl(mockTenantId, 'deleted-file')).rejects.toThrow(common_1.NotFoundException);
        });
    });
    describe('updateFile - additional cases', () => {
        it('should merge metadata with existing metadata', async () => {
            const existingFile = {
                ...mockFile,
                metadata: { existing: 'value' },
            };
            fileRepo.findOne.mockResolvedValue(existingFile);
            fileRepo.save.mockImplementation((file) => Promise.resolve(file));
            const result = await service.updateFile(mockTenantId, 'file-001', {
                metadata: { new: 'data' },
            });
            expect(result.metadata).toEqual({ existing: 'value', new: 'data' });
        });
        it('should not modify file when no update fields provided', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockResolvedValue(mockFile);
            const result = await service.updateFile(mockTenantId, 'file-001', {});
            expect(result.folder).toBe(mockFile.folder);
            expect(result.visibility).toBe(mockFile.visibility);
        });
        it('should update visibility to tenant level', async () => {
            fileRepo.findOne.mockResolvedValue(mockFile);
            fileRepo.save.mockImplementation((file) => Promise.resolve(file));
            const result = await service.updateFile(mockTenantId, 'file-001', {
                visibility: file_entity_1.FileVisibility.TENANT,
            });
            expect(result.visibility).toBe(file_entity_1.FileVisibility.TENANT);
        });
    });
});
//# sourceMappingURL=storage.service.spec.js.map