import 'package:core/presentation/utils/html_transformer/html_transform.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:html/parser.dart'; import 'package:http_parser/http_parser.dart' show MediaType; import 'package:jmap_dart_client/jmap/core/id.dart' show Id; import 'package:model/email/attachment.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; import 'package:uuid/uuid.dart'; import 'html_analyzer_test.mocks.dart'; @GenerateMocks([HtmlTransform, FileUploader, Uuid]) void main() { group('HtmlAnalyzer::replaceImageBase64ToImageCID::', () { late HtmlAnalyzer htmlAnalyzer; late MockHtmlTransform mockHtmlTransform; late MockFileUploader mockFileUploader; late MockUuid mockUuid; setUp(() { mockHtmlTransform = MockHtmlTransform(); mockFileUploader = MockFileUploader(); mockUuid = MockUuid(); htmlAnalyzer = HtmlAnalyzer(mockHtmlTransform, mockFileUploader, mockUuid); }); test( 'When HTML content has no base64 images,\n' 'should return original content and empty attachments if no inline attachments exist', () async { const htmlContent = '
Hello World
'; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); expect(result.value1, htmlContent); expect(result.value2, isEmpty); }); test( 'When HTML content has an image with existing CID,\n' 'should update src to CID format and include attachment in result', () async { const htmlContent = ''; final inlineAttachments = { 'test123': Attachment( blobId: Id('test123'), type: MediaType('image', 'png'), disposition: ContentDisposition.inline, cid: 'test123', ) }; final uploadUri = Uri.parse('https://example.com/upload'); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTag = document.querySelector('img'); expect(imgTag?.attributes['src'], 'cid:test123'); expect(imgTag?.attributes.containsKey('id'), isFalse); expect(result.value2.length, 1); expect(result.value2.first.cid, 'test123'); }); test( 'When HTML content has an image with existing CID,\n' 'but inlineAttachments is empty,\n' 'should re-upload base64 succeeds then replace src with that CID and add attachment to result', () async { const htmlContent = ''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockFileUploader.uploadAttachment(any, any, any)) .thenAnswer((_) async => Attachment( blobId: Id('test123'), type: MediaType('image', 'png'), disposition: ContentDisposition.inline, cid: 'test123', )); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTag = document.querySelector('img'); expect(imgTag?.attributes['src'], 'cid:test123'); expect(imgTag?.attributes.containsKey('id'), isFalse); expect(result.value2.length, 1); expect(result.value2.first.cid, 'test123'); }); test( 'When HTML content has a base64 image and upload succeeds,\n' 'should replace src with new CID and add attachment to result', () async { const base64Data = 'iVBORw0KGgo='; const htmlContent = ''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockUuid.v1()).thenReturn('new-uuid-123'); when(mockFileUploader.uploadAttachment(any, any, any)) .thenAnswer((_) async => Attachment( blobId: Id('blob123'), type: MediaType('image', 'png'), disposition: ContentDisposition.inline, )); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTag = document.querySelector('img'); expect(imgTag?.attributes['src'], 'cid:new-uuid-123'); expect(result.value2.length, 1); expect(result.value2.first.cid, 'new-uuid-123'); verify(mockUuid.v1()).called(1); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(1); }); test( 'When upload URI is null and HTML has base64 images,\n' 'should keep original src and not attempt uploads', () async { const htmlContent = ''; final inlineAttachments = {}; final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: null, ); expect(result.value1, htmlContent); expect(result.value2, isEmpty); verifyNever(mockFileUploader.uploadAttachment(any, any, any)); }); test( 'When processing multiple base64 images,\n' 'should replace each with a unique CID and add corresponding attachments', () async { const htmlContent = ''' '''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockUuid.v1()).thenAnswer((_) => 'uuid1'); when(mockFileUploader.uploadAttachment(any, any, any)) .thenAnswer((_) async => Attachment( blobId: Id('blob123'), type: MediaType('image', 'png'), disposition: ContentDisposition.inline, )); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTags = document.querySelectorAll('img'); expect(imgTags.length, 2); expect(imgTags[0].attributes['src'], contains('cid:')); expect(imgTags[1].attributes['src'], contains('cid:')); expect(result.value2.length, 2); verify(mockUuid.v1()).called(2); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2); }); test( 'When processing 100 base64 images,\n' 'should replace each with a unique CID and add corresponding attachments', () async { int countImage = 100; final htmlContent = ''' ${List.generate(countImage, (index) => '').join(' ')} '''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockUuid.v1()).thenAnswer((_) => 'uuid1'); when(mockFileUploader.uploadAttachment(any, any, any)) .thenAnswer((_) async => Attachment( blobId: Id('blob123'), type: MediaType('image', 'png'), disposition: ContentDisposition.inline, )); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTags = document.querySelectorAll('img'); expect(imgTags.length, countImage); expect(imgTags.every((img) => img.attributes['src']!.contains('cid:')), isTrue); expect(result.value2.length, countImage); verify(mockUuid.v1()).called(countImage); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(countImage); }); test( 'When one image upload throws an exception out of multiple base64 images,\n' 'should replace successful upload with CID and keep original src for failed one', () async { const htmlContent = ''' '''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockUuid.v1()).thenAnswer((_) => 'uuid1'); int uploadCallCount = 0; when(mockFileUploader.uploadAttachment(any, any, any)) .thenAnswer((_) async { uploadCallCount++; if (uploadCallCount == 1) { return Attachment( blobId: Id('blob123'), type: MediaType('image', 'png'), disposition: ContentDisposition.inline, ); } else { throw Exception('Upload failed for second image'); } }); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTags = document.querySelectorAll('img'); expect(imgTags.length, 2); expect(imgTags[0].attributes['src'], 'cid:uuid1'); expect(imgTags[1].attributes['src'], 'data:image/jpeg;base64,/9j/4AAQSkZJRg=='); expect(result.value2.length, 1); expect(result.value2.first.cid, 'uuid1'); verify(mockUuid.v1()).called(2); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2); }); test( 'When all uploads throw exceptions for multiple base64 images,\n' 'should keep original src for all images and add no attachments', () async { const htmlContent = ''' '''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockUuid.v1()).thenAnswer((_) => 'uuid1'); when(mockFileUploader.uploadAttachment(any, any, any)) .thenThrow(Exception('Upload failed')); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTags = document.querySelectorAll('img'); expect(imgTags.length, 2); expect(imgTags[0].attributes['src'], 'data:image/png;base64,iVBORw0KGgo='); expect(imgTags[1].attributes['src'], 'data:image/jpeg;base64,/9j/4AAQSkZJRg=='); expect(result.value2.length, 0); verify(mockUuid.v1()).called(2); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2); }); test( 'When processing an invalid base64 image that causes upload to throw an exception,\n' 'should keep the original invalid src and add no attachments', () async { const htmlContent = ''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockUuid.v1()).thenAnswer((_) => 'uuid1'); when(mockFileUploader.uploadAttachment(any, any, any)) .thenThrow(Exception('Invalid base64 data')); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTags = document.querySelectorAll('img'); expect(imgTags.length, 1); expect(imgTags[0].attributes['src'], 'data:image/png;base64,invalid-base64-data'); expect(result.value2.length, 0); verify(mockUuid.v1()).called(1); }); test( 'When upload throws an exception for a base64 image,\n' 'should keep the original src and add no attachments', () async { const htmlContent = ''; final inlineAttachments = {}; final uploadUri = Uri.parse('https://example.com/upload'); when(mockUuid.v1()).thenAnswer((_) => 'uuid1'); when(mockFileUploader.uploadAttachment(any, any, any)) .thenThrow(Exception('Upload failed')); final result = await htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: htmlContent, inlineAttachments: inlineAttachments, uploadUri: uploadUri, ); final document = parse(result.value1); final imgTags = document.querySelectorAll('img'); expect(imgTags.length, 1); expect(imgTags[0].attributes['src'], 'data:image/png;base64,iVBORw0KGgo='); expect(result.value2.length, 0); verify(mockUuid.v1()).called(1); verify(mockFileUploader.uploadAttachment(any, any, any)).called(1); }); }); }