import { AvatarGenerationRequest } from './AvatarGenerationRequest'; import { MediaUrl } from '../value-objects/MediaUrl'; describe('AvatarGenerationRequest', () => { describe('create', () => { it('creates a new request with required properties', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', }); expect(request.id).toBe('req-1'); expect(request.userId).toBe('user-1'); expect(request.facePhotoUrl).toBeInstanceOf(MediaUrl); expect(request.facePhotoUrl.value).toBe('data:image/png;base64,abc'); expect(request.suitColor).toBe('red'); expect(request.style).toBe('realistic'); expect(request.status).toBe('pending'); expect(request.generatedAvatarUrls).toEqual([]); expect(request.selectedAvatarIndex).toBeUndefined(); expect(request.errorMessage).toBeUndefined(); expect(request.createdAt).toBeInstanceOf(Date); expect(request.updatedAt).toBeInstanceOf(Date); }); it('creates request with default style when not provided', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'blue', }); expect(request.style).toBe('realistic'); }); it('throws error when userId is missing', () => { expect(() => AvatarGenerationRequest.create({ id: 'req-1', userId: '', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }) ).toThrow('User ID is required'); }); it('throws error when facePhotoUrl is missing', () => { expect(() => AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: '', suitColor: 'red', }) ).toThrow('Face photo URL is required'); }); it('throws error when facePhotoUrl is invalid', () => { expect(() => AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'invalid-url', suitColor: 'red', }) ).toThrow(); }); }); describe('reconstitute', () => { it('reconstitutes a request from props', () => { const createdAt = new Date('2024-01-01T00:00:00.000Z'); const updatedAt = new Date('2024-01-01T01:00:00.000Z'); const request = AvatarGenerationRequest.reconstitute({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', status: 'pending', generatedAvatarUrls: [], createdAt, updatedAt, }); expect(request.id).toBe('req-1'); expect(request.userId).toBe('user-1'); expect(request.facePhotoUrl.value).toBe('data:image/png;base64,abc'); expect(request.suitColor).toBe('red'); expect(request.style).toBe('realistic'); expect(request.status).toBe('pending'); expect(request.generatedAvatarUrls).toEqual([]); expect(request.selectedAvatarIndex).toBeUndefined(); expect(request.errorMessage).toBeUndefined(); expect(request.createdAt).toEqual(createdAt); expect(request.updatedAt).toEqual(updatedAt); }); it('reconstitutes a request with selected avatar', () => { const request = AvatarGenerationRequest.reconstitute({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', status: 'completed', generatedAvatarUrls: ['https://example.com/a.png', 'https://example.com/b.png'], selectedAvatarIndex: 1, createdAt: new Date(), updatedAt: new Date(), }); expect(request.selectedAvatarIndex).toBe(1); expect(request.selectedAvatarUrl).toBe('https://example.com/b.png'); }); it('reconstitutes a failed request', () => { const request = AvatarGenerationRequest.reconstitute({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', status: 'failed', generatedAvatarUrls: [], errorMessage: 'Generation failed', createdAt: new Date(), updatedAt: new Date(), }); expect(request.status).toBe('failed'); expect(request.errorMessage).toBe('Generation failed'); }); }); describe('status transitions', () => { it('transitions from pending to validating', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); expect(request.status).toBe('pending'); request.markAsValidating(); expect(request.status).toBe('validating'); }); it('transitions from validating to generating', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); request.markAsGenerating(); expect(request.status).toBe('generating'); }); it('throws error when marking as validating from non-pending status', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); expect(() => request.markAsValidating()).toThrow('Can only start validation from pending status'); }); it('throws error when marking as generating from non-validating status', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); expect(() => request.markAsGenerating()).toThrow('Can only start generation from validating status'); }); it('completes request with avatars', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); request.markAsGenerating(); request.completeWithAvatars(['https://example.com/a.png', 'https://example.com/b.png']); expect(request.status).toBe('completed'); expect(request.generatedAvatarUrls).toEqual(['https://example.com/a.png', 'https://example.com/b.png']); }); it('throws error when completing with empty avatar list', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); request.markAsGenerating(); expect(() => request.completeWithAvatars([])).toThrow('At least one avatar URL is required'); }); it('fails request with error message', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); request.fail('Face validation failed'); expect(request.status).toBe('failed'); expect(request.errorMessage).toBe('Face validation failed'); }); }); describe('avatar selection', () => { it('selects avatar when request is completed', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); request.markAsGenerating(); request.completeWithAvatars(['https://example.com/a.png', 'https://example.com/b.png']); request.selectAvatar(1); expect(request.selectedAvatarIndex).toBe(1); expect(request.selectedAvatarUrl).toBe('https://example.com/b.png'); }); it('throws error when selecting avatar from non-completed request', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); expect(() => request.selectAvatar(0)).toThrow('Can only select avatar when generation is completed'); }); it('throws error when selecting invalid index', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); request.markAsGenerating(); request.completeWithAvatars(['https://example.com/a.png', 'https://example.com/b.png']); expect(() => request.selectAvatar(-1)).toThrow('Invalid avatar index'); expect(() => request.selectAvatar(2)).toThrow('Invalid avatar index'); }); it('returns undefined for selectedAvatarUrl when no avatar selected', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); request.markAsValidating(); request.markAsGenerating(); request.completeWithAvatars(['https://example.com/a.png', 'https://example.com/b.png']); expect(request.selectedAvatarUrl).toBeUndefined(); }); }); describe('buildPrompt', () => { it('builds prompt for red suit, realistic style', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', }); const prompt = request.buildPrompt(); expect(prompt).toContain('vibrant racing red'); expect(prompt).toContain('photorealistic, professional motorsport portrait'); expect(prompt).toContain('racing driver'); expect(prompt).toContain('racing suit'); expect(prompt).toContain('helmet'); }); it('builds prompt for blue suit, cartoon style', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'blue', style: 'cartoon', }); const prompt = request.buildPrompt(); expect(prompt).toContain('deep motorsport blue'); expect(prompt).toContain('stylized cartoon racing character'); }); it('builds prompt for pixel-art style', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'green', style: 'pixel-art', }); const prompt = request.buildPrompt(); expect(prompt).toContain('racing green'); expect(prompt).toContain('8-bit pixel art retro racing avatar'); }); it('builds prompt for all suit colors', () => { const colors = ['red', 'blue', 'green', 'yellow', 'orange', 'purple', 'black', 'white', 'pink', 'cyan'] as const; colors.forEach((color) => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: color, }); const prompt = request.buildPrompt(); expect(prompt).toContain(color); }); }); }); describe('toProps', () => { it('returns correct props for a new request', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', }); const props = request.toProps(); expect(props.id).toBe('req-1'); expect(props.userId).toBe('user-1'); expect(props.facePhotoUrl).toBe('data:image/png;base64,abc'); expect(props.suitColor).toBe('red'); expect(props.style).toBe('realistic'); expect(props.status).toBe('pending'); expect(props.generatedAvatarUrls).toEqual([]); expect(props.selectedAvatarIndex).toBeUndefined(); expect(props.errorMessage).toBeUndefined(); expect(props.createdAt).toBeInstanceOf(Date); expect(props.updatedAt).toBeInstanceOf(Date); }); it('returns correct props for a completed request with selected avatar', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', }); request.markAsValidating(); request.markAsGenerating(); request.completeWithAvatars(['https://example.com/a.png', 'https://example.com/b.png']); request.selectAvatar(1); const props = request.toProps(); expect(props.id).toBe('req-1'); expect(props.userId).toBe('user-1'); expect(props.facePhotoUrl).toBe('data:image/png;base64,abc'); expect(props.suitColor).toBe('red'); expect(props.style).toBe('realistic'); expect(props.status).toBe('completed'); expect(props.generatedAvatarUrls).toEqual(['https://example.com/a.png', 'https://example.com/b.png']); expect(props.selectedAvatarIndex).toBe(1); expect(props.errorMessage).toBeUndefined(); }); it('returns correct props for a failed request', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', style: 'realistic', }); request.markAsValidating(); request.fail('Face validation failed'); const props = request.toProps(); expect(props.id).toBe('req-1'); expect(props.userId).toBe('user-1'); expect(props.facePhotoUrl).toBe('data:image/png;base64,abc'); expect(props.suitColor).toBe('red'); expect(props.style).toBe('realistic'); expect(props.status).toBe('failed'); expect(props.generatedAvatarUrls).toEqual([]); expect(props.selectedAvatarIndex).toBeUndefined(); expect(props.errorMessage).toBe('Face validation failed'); }); }); describe('value object validation', () => { it('validates facePhotoUrl as MediaUrl value object', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'data:image/png;base64,abc', suitColor: 'red', }); expect(request.facePhotoUrl).toBeInstanceOf(MediaUrl); expect(request.facePhotoUrl.value).toBe('data:image/png;base64,abc'); }); it('accepts http URL for facePhotoUrl', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: 'https://example.com/face.png', suitColor: 'red', }); expect(request.facePhotoUrl.value).toBe('https://example.com/face.png'); }); it('accepts root-relative path for facePhotoUrl', () => { const request = AvatarGenerationRequest.create({ id: 'req-1', userId: 'user-1', facePhotoUrl: '/images/face.png', suitColor: 'red', }); expect(request.facePhotoUrl.value).toBe('/images/face.png'); }); }); });