import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MediaService } from './MediaService'; import type { MediaApiClient } from '../../api/media/MediaApiClient'; import type { MediaPresenter } from '../../presenters/MediaPresenter'; import type { UploadMediaInputDto, UploadMediaOutputDto, GetMediaOutputDto, DeleteMediaOutputDto } from '../../dtos'; import type { UploadMediaViewModel, MediaViewModel, DeleteMediaViewModel } from '../../view-models'; describe('MediaService', () => { let service: MediaService; let mockApiClient: MediaApiClient; let mockPresenter: MediaPresenter; beforeEach(() => { mockApiClient = { uploadMedia: vi.fn(), getMedia: vi.fn(), deleteMedia: vi.fn(), } as unknown as MediaApiClient; mockPresenter = { presentUpload: vi.fn(), presentMedia: vi.fn(), presentDelete: vi.fn(), } as unknown as MediaPresenter; service = new MediaService(mockApiClient, mockPresenter); }); describe('constructor', () => { it('should create instance with injected dependencies', () => { expect(service).toBeInstanceOf(MediaService); }); }); describe('uploadMedia', () => { it('should upload media and transform via presenter', async () => { // Arrange const input: UploadMediaInputDto = { file: new File(['test'], 'test.jpg', { type: 'image/jpeg' }), type: 'image', category: 'avatar', }; const mockDto: UploadMediaOutputDto = { success: true, mediaId: 'media-123', url: 'https://example.com/media/test.jpg', }; const mockViewModel: UploadMediaViewModel = { success: true, mediaId: 'media-123', url: 'https://example.com/media/test.jpg', }; vi.mocked(mockApiClient.uploadMedia).mockResolvedValue(mockDto); vi.mocked(mockPresenter.presentUpload).mockReturnValue(mockViewModel); // Act const result = await service.uploadMedia(input); // Assert expect(mockApiClient.uploadMedia).toHaveBeenCalledWith(input); expect(mockPresenter.presentUpload).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); it('should handle upload failure', async () => { // Arrange const input: UploadMediaInputDto = { file: new File(['test'], 'test.jpg', { type: 'image/jpeg' }), type: 'image', }; const mockDto: UploadMediaOutputDto = { success: false, error: 'Upload failed', }; const mockViewModel: UploadMediaViewModel = { success: false, error: 'Upload failed', }; vi.mocked(mockApiClient.uploadMedia).mockResolvedValue(mockDto); vi.mocked(mockPresenter.presentUpload).mockReturnValue(mockViewModel); // Act const result = await service.uploadMedia(input); // Assert expect(mockApiClient.uploadMedia).toHaveBeenCalledWith(input); expect(mockPresenter.presentUpload).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); expect(result.success).toBe(false); }); it('should propagate errors from API client', async () => { // Arrange const input: UploadMediaInputDto = { file: new File(['test'], 'test.jpg', { type: 'image/jpeg' }), type: 'image', }; const error = new Error('Network error'); vi.mocked(mockApiClient.uploadMedia).mockRejectedValue(error); // Act & Assert await expect(service.uploadMedia(input)).rejects.toThrow('Network error'); expect(mockApiClient.uploadMedia).toHaveBeenCalledWith(input); expect(mockPresenter.presentUpload).not.toHaveBeenCalled(); }); }); describe('getMedia', () => { it('should fetch media and transform via presenter', async () => { // Arrange const mediaId = 'media-123'; const mockDto: GetMediaOutputDto = { id: 'media-123', url: 'https://example.com/media/test.jpg', type: 'image', category: 'avatar', uploadedAt: '2023-01-01T00:00:00Z', size: 1024, }; const mockViewModel: MediaViewModel = { id: 'media-123', url: 'https://example.com/media/test.jpg', type: 'image', category: 'avatar', uploadedAt: new Date('2023-01-01T00:00:00Z'), size: 1024, }; vi.mocked(mockApiClient.getMedia).mockResolvedValue(mockDto); vi.mocked(mockPresenter.presentMedia).mockReturnValue(mockViewModel); // Act const result = await service.getMedia(mediaId); // Assert expect(mockApiClient.getMedia).toHaveBeenCalledWith(mediaId); expect(mockPresenter.presentMedia).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); it('should handle media without optional fields', async () => { // Arrange const mediaId = 'media-123'; const mockDto: GetMediaOutputDto = { id: 'media-123', url: 'https://example.com/media/test.jpg', type: 'image', uploadedAt: '2023-01-01T00:00:00Z', }; const mockViewModel: MediaViewModel = { id: 'media-123', url: 'https://example.com/media/test.jpg', type: 'image', uploadedAt: new Date('2023-01-01T00:00:00Z'), }; vi.mocked(mockApiClient.getMedia).mockResolvedValue(mockDto); vi.mocked(mockPresenter.presentMedia).mockReturnValue(mockViewModel); // Act const result = await service.getMedia(mediaId); // Assert expect(mockApiClient.getMedia).toHaveBeenCalledWith(mediaId); expect(mockPresenter.presentMedia).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); it('should propagate errors from API client', async () => { // Arrange const mediaId = 'media-123'; const error = new Error('Media not found'); vi.mocked(mockApiClient.getMedia).mockRejectedValue(error); // Act & Assert await expect(service.getMedia(mediaId)).rejects.toThrow('Media not found'); expect(mockApiClient.getMedia).toHaveBeenCalledWith(mediaId); expect(mockPresenter.presentMedia).not.toHaveBeenCalled(); }); }); describe('deleteMedia', () => { it('should delete media and transform via presenter', async () => { // Arrange const mediaId = 'media-123'; const mockDto: DeleteMediaOutputDto = { success: true, }; const mockViewModel: DeleteMediaViewModel = { success: true, }; vi.mocked(mockApiClient.deleteMedia).mockResolvedValue(mockDto); vi.mocked(mockPresenter.presentDelete).mockReturnValue(mockViewModel); // Act const result = await service.deleteMedia(mediaId); // Assert expect(mockApiClient.deleteMedia).toHaveBeenCalledWith(mediaId); expect(mockPresenter.presentDelete).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); }); it('should handle delete failure', async () => { // Arrange const mediaId = 'media-123'; const mockDto: DeleteMediaOutputDto = { success: false, error: 'Delete failed', }; const mockViewModel: DeleteMediaViewModel = { success: false, error: 'Delete failed', }; vi.mocked(mockApiClient.deleteMedia).mockResolvedValue(mockDto); vi.mocked(mockPresenter.presentDelete).mockReturnValue(mockViewModel); // Act const result = await service.deleteMedia(mediaId); // Assert expect(mockApiClient.deleteMedia).toHaveBeenCalledWith(mediaId); expect(mockPresenter.presentDelete).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModel); expect(result.success).toBe(false); }); it('should propagate errors from API client', async () => { // Arrange const mediaId = 'media-123'; const error = new Error('Delete failed'); vi.mocked(mockApiClient.deleteMedia).mockRejectedValue(error); // Act & Assert await expect(service.deleteMedia(mediaId)).rejects.toThrow('Delete failed'); expect(mockApiClient.deleteMedia).toHaveBeenCalledWith(mediaId); expect(mockPresenter.presentDelete).not.toHaveBeenCalled(); }); }); });