test: Add Photos service tests (506 lines, 24 tests)
Created comprehensive test suite for photo management service: - Upload photo with thumbnail generation and optimization - File storage integration (original + thumbnail) - Get photos by child/activity with filtering and pagination - Photo metadata management (caption, description, type) - Presigned URL generation for secure downloads - Gallery view with URLs - Update photo metadata - Delete photo from storage and database - Milestone photo tracking - Recent photos with child relations - Photo statistics (total, by type, file size) Tests cover: - Success cases with storage service integration - Error handling (not found, upload failures, storage errors) - Edge cases (no thumbnail, empty collections) - Filtering (by type, limit, offset) - Audit logging integration Total: 506 lines, 24 test cases Coverage: Photo upload, gallery, CRUD, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,506 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { NotFoundException } from '@nestjs/common';
|
||||||
|
import { PhotosService, UploadPhotoDto } from './photos.service';
|
||||||
|
import { Photo, PhotoType } from '../../database/entities/photo.entity';
|
||||||
|
import { StorageService } from '../../common/services/storage.service';
|
||||||
|
import { AuditService } from '../../common/services/audit.service';
|
||||||
|
|
||||||
|
describe('PhotosService', () => {
|
||||||
|
let service: PhotosService;
|
||||||
|
let photoRepository: Repository<Photo>;
|
||||||
|
let storageService: StorageService;
|
||||||
|
let auditService: AuditService;
|
||||||
|
|
||||||
|
const mockPhoto = {
|
||||||
|
id: 'photo_123',
|
||||||
|
userId: 'user_123',
|
||||||
|
childId: 'child_123',
|
||||||
|
activityId: null,
|
||||||
|
type: PhotoType.GENERAL,
|
||||||
|
originalFilename: 'baby_smile.jpg',
|
||||||
|
mimeType: 'image/jpeg',
|
||||||
|
fileSize: 1024000,
|
||||||
|
storageKey: 'photos/user_123/child_123/abc123.jpg',
|
||||||
|
thumbnailKey: 'photos/user_123/child_123/thumbnails/abc123_thumb.jpg',
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
caption: 'Beautiful smile!',
|
||||||
|
description: null,
|
||||||
|
takenAt: new Date('2025-01-01'),
|
||||||
|
metadata: null,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockFile: Express.Multer.File = {
|
||||||
|
fieldname: 'photo',
|
||||||
|
originalname: 'test.jpg',
|
||||||
|
encoding: '7bit',
|
||||||
|
mimetype: 'image/jpeg',
|
||||||
|
size: 1024000,
|
||||||
|
buffer: Buffer.from('fake-image-data'),
|
||||||
|
stream: null as any,
|
||||||
|
destination: '',
|
||||||
|
filename: '',
|
||||||
|
path: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockPhotoRepository = {
|
||||||
|
create: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
find: jest.fn(),
|
||||||
|
findOne: jest.fn(),
|
||||||
|
remove: jest.fn(),
|
||||||
|
createQueryBuilder: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockStorageService = {
|
||||||
|
uploadImage: jest.fn().mockResolvedValue({
|
||||||
|
key: 'photos/user_123/child_123/abc123.jpg',
|
||||||
|
url: 'https://storage.com/photo.jpg',
|
||||||
|
metadata: {
|
||||||
|
size: 1024000,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
generateThumbnail: jest.fn().mockResolvedValue({
|
||||||
|
key: 'photos/user_123/child_123/thumbnails/abc123_thumb.jpg',
|
||||||
|
url: 'https://storage.com/thumb.jpg',
|
||||||
|
}),
|
||||||
|
getPresignedUrl: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue('https://storage.com/presigned/photo.jpg'),
|
||||||
|
deleteFile: jest.fn().mockResolvedValue(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockAuditService = {
|
||||||
|
logCreate: jest.fn().mockResolvedValue(undefined),
|
||||||
|
logDelete: jest.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
PhotosService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Photo),
|
||||||
|
useValue: mockPhotoRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: StorageService,
|
||||||
|
useValue: mockStorageService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AuditService,
|
||||||
|
useValue: mockAuditService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<PhotosService>(PhotosService);
|
||||||
|
photoRepository = module.get<Repository<Photo>>(
|
||||||
|
getRepositoryToken(Photo),
|
||||||
|
);
|
||||||
|
storageService = module.get<StorageService>(StorageService);
|
||||||
|
auditService = module.get<AuditService>(AuditService);
|
||||||
|
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('uploadPhoto', () => {
|
||||||
|
it('should upload photo with thumbnail generation', async () => {
|
||||||
|
const uploadDto: UploadPhotoDto = {
|
||||||
|
userId: 'user_123',
|
||||||
|
childId: 'child_123',
|
||||||
|
type: PhotoType.GENERAL,
|
||||||
|
caption: 'Test photo',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPhotoRepository.create.mockReturnValue(mockPhoto);
|
||||||
|
mockPhotoRepository.save.mockResolvedValue(mockPhoto);
|
||||||
|
|
||||||
|
const result = await service.uploadPhoto(mockFile, uploadDto);
|
||||||
|
|
||||||
|
expect(storageService.uploadImage).toHaveBeenCalledWith(
|
||||||
|
mockFile.buffer,
|
||||||
|
expect.stringContaining('photos/user_123/child_123'),
|
||||||
|
{
|
||||||
|
maxWidth: 1920,
|
||||||
|
maxHeight: 1920,
|
||||||
|
quality: 85,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(storageService.generateThumbnail).toHaveBeenCalledWith(
|
||||||
|
mockFile.buffer,
|
||||||
|
expect.stringContaining('thumbnails'),
|
||||||
|
300,
|
||||||
|
300,
|
||||||
|
);
|
||||||
|
expect(photoRepository.save).toHaveBeenCalled();
|
||||||
|
expect(auditService.logCreate).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockPhoto);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle photo upload without childId', async () => {
|
||||||
|
const uploadDto: UploadPhotoDto = {
|
||||||
|
userId: 'user_123',
|
||||||
|
type: PhotoType.GENERAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPhotoRepository.create.mockReturnValue(mockPhoto);
|
||||||
|
mockPhotoRepository.save.mockResolvedValue(mockPhoto);
|
||||||
|
|
||||||
|
await service.uploadPhoto(mockFile, uploadDto);
|
||||||
|
|
||||||
|
expect(storageService.uploadImage).toHaveBeenCalledWith(
|
||||||
|
mockFile.buffer,
|
||||||
|
expect.stringContaining('general'),
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if upload fails', async () => {
|
||||||
|
mockStorageService.uploadImage.mockRejectedValue(
|
||||||
|
new Error('Storage error'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const uploadDto: UploadPhotoDto = {
|
||||||
|
userId: 'user_123',
|
||||||
|
type: PhotoType.GENERAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(service.uploadPhoto(mockFile, uploadDto)).rejects.toThrow(
|
||||||
|
'Photo upload failed',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include optional metadata', async () => {
|
||||||
|
const uploadDto: UploadPhotoDto = {
|
||||||
|
userId: 'user_123',
|
||||||
|
childId: 'child_123',
|
||||||
|
type: PhotoType.MILESTONE,
|
||||||
|
caption: 'First steps',
|
||||||
|
description: 'Baby walked for the first time!',
|
||||||
|
takenAt: new Date('2025-01-01'),
|
||||||
|
metadata: { milestone: 'first_steps', ageMonths: 12 },
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPhotoRepository.create.mockReturnValue(mockPhoto);
|
||||||
|
mockPhotoRepository.save.mockResolvedValue(mockPhoto);
|
||||||
|
|
||||||
|
await service.uploadPhoto(mockFile, uploadDto);
|
||||||
|
|
||||||
|
expect(photoRepository.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
caption: 'First steps',
|
||||||
|
description: 'Baby walked for the first time!',
|
||||||
|
takenAt: uploadDto.takenAt,
|
||||||
|
metadata: uploadDto.metadata,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getChildPhotos', () => {
|
||||||
|
it('should get all photos for a child', async () => {
|
||||||
|
const mockQueryBuilder = {
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
andWhere: jest.fn().mockReturnThis(),
|
||||||
|
orderBy: jest.fn().mockReturnThis(),
|
||||||
|
take: jest.fn().mockReturnThis(),
|
||||||
|
skip: jest.fn().mockReturnThis(),
|
||||||
|
getCount: jest.fn().mockResolvedValue(5),
|
||||||
|
getMany: jest.fn().mockResolvedValue([mockPhoto, mockPhoto]),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPhotoRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
||||||
|
|
||||||
|
const result = await service.getChildPhotos('child_123');
|
||||||
|
|
||||||
|
expect(result.photos).toHaveLength(2);
|
||||||
|
expect(result.total).toBe(5);
|
||||||
|
expect(mockQueryBuilder.where).toHaveBeenCalledWith(
|
||||||
|
'photo.childId = :childId',
|
||||||
|
{ childId: 'child_123' },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by photo type', async () => {
|
||||||
|
const mockQueryBuilder = {
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
andWhere: jest.fn().mockReturnThis(),
|
||||||
|
orderBy: jest.fn().mockReturnThis(),
|
||||||
|
take: jest.fn().mockReturnThis(),
|
||||||
|
skip: jest.fn().mockReturnThis(),
|
||||||
|
getCount: jest.fn().mockResolvedValue(3),
|
||||||
|
getMany: jest.fn().mockResolvedValue([mockPhoto]),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPhotoRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
||||||
|
|
||||||
|
await service.getChildPhotos('child_123', {
|
||||||
|
type: PhotoType.MILESTONE,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith(
|
||||||
|
'photo.type = :type',
|
||||||
|
{ type: PhotoType.MILESTONE },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply limit and offset', async () => {
|
||||||
|
const mockQueryBuilder = {
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
andWhere: jest.fn().mockReturnThis(),
|
||||||
|
orderBy: jest.fn().mockReturnThis(),
|
||||||
|
take: jest.fn().mockReturnThis(),
|
||||||
|
skip: jest.fn().mockReturnThis(),
|
||||||
|
getCount: jest.fn().mockResolvedValue(100),
|
||||||
|
getMany: jest.fn().mockResolvedValue([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPhotoRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
||||||
|
|
||||||
|
await service.getChildPhotos('child_123', {
|
||||||
|
limit: 10,
|
||||||
|
offset: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryBuilder.take).toHaveBeenCalledWith(10);
|
||||||
|
expect(mockQueryBuilder.skip).toHaveBeenCalledWith(20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getActivityPhotos', () => {
|
||||||
|
it('should get photos for an activity', async () => {
|
||||||
|
mockPhotoRepository.find.mockResolvedValue([mockPhoto]);
|
||||||
|
|
||||||
|
const result = await service.getActivityPhotos('activity_123');
|
||||||
|
|
||||||
|
expect(photoRepository.find).toHaveBeenCalledWith({
|
||||||
|
where: { activityId: 'activity_123' },
|
||||||
|
order: { createdAt: 'ASC' },
|
||||||
|
});
|
||||||
|
expect(result).toEqual([mockPhoto]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPhoto', () => {
|
||||||
|
it('should get photo by ID', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(mockPhoto);
|
||||||
|
|
||||||
|
const result = await service.getPhoto('photo_123', 'user_123');
|
||||||
|
|
||||||
|
expect(photoRepository.findOne).toHaveBeenCalledWith({
|
||||||
|
where: { id: 'photo_123', userId: 'user_123' },
|
||||||
|
relations: ['child', 'activity'],
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockPhoto);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NotFoundException if photo not found', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(service.getPhoto('photo_123', 'user_123')).rejects.toThrow(
|
||||||
|
NotFoundException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPhotoWithUrl', () => {
|
||||||
|
it('should return photo with presigned URLs', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(mockPhoto);
|
||||||
|
|
||||||
|
const result = await service.getPhotoWithUrl('photo_123', 'user_123');
|
||||||
|
|
||||||
|
expect(storageService.getPresignedUrl).toHaveBeenCalledWith(
|
||||||
|
mockPhoto.storageKey,
|
||||||
|
3600,
|
||||||
|
);
|
||||||
|
expect(storageService.getPresignedUrl).toHaveBeenCalledWith(
|
||||||
|
mockPhoto.thumbnailKey,
|
||||||
|
3600,
|
||||||
|
);
|
||||||
|
expect(result).toHaveProperty('url');
|
||||||
|
expect(result).toHaveProperty('thumbnailUrl');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use main URL for thumbnail if no thumbnail key', async () => {
|
||||||
|
const photoWithoutThumbnail = { ...mockPhoto, thumbnailKey: null };
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(photoWithoutThumbnail);
|
||||||
|
|
||||||
|
const result = await service.getPhotoWithUrl('photo_123', 'user_123');
|
||||||
|
|
||||||
|
expect(result.url).toBe(result.thumbnailUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getGallery', () => {
|
||||||
|
it('should get photos with presigned URLs', async () => {
|
||||||
|
const mockQueryBuilder = {
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
andWhere: jest.fn().mockReturnThis(),
|
||||||
|
orderBy: jest.fn().mockReturnThis(),
|
||||||
|
take: jest.fn().mockReturnThis(),
|
||||||
|
skip: jest.fn().mockReturnThis(),
|
||||||
|
getCount: jest.fn().mockResolvedValue(2),
|
||||||
|
getMany: jest.fn().mockResolvedValue([mockPhoto, mockPhoto]),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPhotoRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
||||||
|
|
||||||
|
const result = await service.getGallery('child_123', 'user_123');
|
||||||
|
|
||||||
|
expect(result.total).toBe(2);
|
||||||
|
expect(result.photos).toHaveLength(2);
|
||||||
|
expect(result.photos[0]).toHaveProperty('url');
|
||||||
|
expect(result.photos[0]).toHaveProperty('thumbnailUrl');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updatePhoto', () => {
|
||||||
|
it('should update photo metadata', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(mockPhoto);
|
||||||
|
const updatedPhoto = { ...mockPhoto, caption: 'Updated caption' };
|
||||||
|
mockPhotoRepository.save.mockResolvedValue(updatedPhoto);
|
||||||
|
|
||||||
|
const result = await service.updatePhoto('photo_123', 'user_123', {
|
||||||
|
caption: 'Updated caption',
|
||||||
|
description: 'New description',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(photoRepository.save).toHaveBeenCalled();
|
||||||
|
expect(result.caption).toBe('Updated caption');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if photo not found', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.updatePhoto('photo_123', 'user_123', { caption: 'Test' }),
|
||||||
|
).rejects.toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deletePhoto', () => {
|
||||||
|
it('should delete photo from storage and database', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(mockPhoto);
|
||||||
|
mockPhotoRepository.remove.mockResolvedValue(mockPhoto);
|
||||||
|
|
||||||
|
await service.deletePhoto('photo_123', 'user_123');
|
||||||
|
|
||||||
|
expect(storageService.deleteFile).toHaveBeenCalledWith(
|
||||||
|
mockPhoto.storageKey,
|
||||||
|
);
|
||||||
|
expect(storageService.deleteFile).toHaveBeenCalledWith(
|
||||||
|
mockPhoto.thumbnailKey,
|
||||||
|
);
|
||||||
|
expect(photoRepository.remove).toHaveBeenCalledWith(mockPhoto);
|
||||||
|
expect(auditService.logDelete).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should continue with database deletion if storage deletion fails', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(mockPhoto);
|
||||||
|
mockPhotoRepository.remove.mockResolvedValue(mockPhoto);
|
||||||
|
mockStorageService.deleteFile.mockRejectedValue(
|
||||||
|
new Error('Storage error'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.deletePhoto('photo_123', 'user_123');
|
||||||
|
|
||||||
|
expect(photoRepository.remove).toHaveBeenCalledWith(mockPhoto);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if photo not found', async () => {
|
||||||
|
mockPhotoRepository.findOne.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.deletePhoto('photo_123', 'user_123'),
|
||||||
|
).rejects.toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getMilestonePhotos', () => {
|
||||||
|
it('should get milestone photos ordered by takenAt', async () => {
|
||||||
|
const milestonePhotos = [
|
||||||
|
{ ...mockPhoto, type: PhotoType.MILESTONE },
|
||||||
|
{ ...mockPhoto, type: PhotoType.MILESTONE },
|
||||||
|
];
|
||||||
|
mockPhotoRepository.find.mockResolvedValue(milestonePhotos);
|
||||||
|
|
||||||
|
const result = await service.getMilestonePhotos('child_123');
|
||||||
|
|
||||||
|
expect(photoRepository.find).toHaveBeenCalledWith({
|
||||||
|
where: { childId: 'child_123', type: PhotoType.MILESTONE },
|
||||||
|
order: { takenAt: 'ASC', createdAt: 'ASC' },
|
||||||
|
});
|
||||||
|
expect(result).toEqual(milestonePhotos);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRecentPhotos', () => {
|
||||||
|
it('should get recent photos with default limit', async () => {
|
||||||
|
mockPhotoRepository.find.mockResolvedValue([mockPhoto]);
|
||||||
|
|
||||||
|
const result = await service.getRecentPhotos('user_123');
|
||||||
|
|
||||||
|
expect(photoRepository.find).toHaveBeenCalledWith({
|
||||||
|
where: { userId: 'user_123' },
|
||||||
|
order: { createdAt: 'DESC' },
|
||||||
|
take: 10,
|
||||||
|
relations: ['child'],
|
||||||
|
});
|
||||||
|
expect(result).toEqual([mockPhoto]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply custom limit', async () => {
|
||||||
|
mockPhotoRepository.find.mockResolvedValue([]);
|
||||||
|
|
||||||
|
await service.getRecentPhotos('user_123', 5);
|
||||||
|
|
||||||
|
expect(photoRepository.find).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
take: 5,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPhotoStats', () => {
|
||||||
|
it('should calculate photo statistics', async () => {
|
||||||
|
const photos = [
|
||||||
|
{ ...mockPhoto, type: PhotoType.MILESTONE, fileSize: 500000 },
|
||||||
|
{ ...mockPhoto, type: PhotoType.MILESTONE, fileSize: 600000 },
|
||||||
|
{ ...mockPhoto, type: PhotoType.ACTIVITY, fileSize: 400000 },
|
||||||
|
{ ...mockPhoto, type: PhotoType.GENERAL, fileSize: 300000 },
|
||||||
|
];
|
||||||
|
mockPhotoRepository.find.mockResolvedValue(photos);
|
||||||
|
|
||||||
|
const result = await service.getPhotoStats('child_123');
|
||||||
|
|
||||||
|
expect(result.total).toBe(4);
|
||||||
|
expect(result.byType[PhotoType.MILESTONE]).toBe(2);
|
||||||
|
expect(result.byType[PhotoType.ACTIVITY]).toBe(1);
|
||||||
|
expect(result.byType[PhotoType.GENERAL]).toBe(1);
|
||||||
|
expect(result.byType[PhotoType.PROFILE]).toBe(0);
|
||||||
|
expect(result.totalSize).toBe(1800000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty photo collection', async () => {
|
||||||
|
mockPhotoRepository.find.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await service.getPhotoStats('child_123');
|
||||||
|
|
||||||
|
expect(result.total).toBe(0);
|
||||||
|
expect(result.totalSize).toBe(0);
|
||||||
|
expect(result.byType[PhotoType.MILESTONE]).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user