TF-3514 Add test case for unit test HtmlAnalyzer when html content existing CID but inlineAttachments is empty

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-03-07 10:41:27 +07:00
committed by Dat H. Pham
parent 4aef1698a4
commit 769bca0dda
2 changed files with 66 additions and 25 deletions
@@ -1,4 +1,3 @@
import 'dart:collection';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:core/data/constants/constant.dart'; import 'package:core/data/constants/constant.dart';
@@ -128,6 +127,7 @@ class HtmlAnalyzer {
}) async { }) async {
final document = parse(emailContent); final document = parse(emailContent);
final listImgTag = document.querySelectorAll('img[src^="data:image/"]'); final listImgTag = document.querySelectorAll('img[src^="data:image/"]');
log('HtmlAnalyzer::replaceImageBase64ToImageCID:listImgTagLength = ${listImgTag.length} | inlineAttachments = ${inlineAttachments.length}'); log('HtmlAnalyzer::replaceImageBase64ToImageCID:listImgTagLength = ${listImgTag.length} | inlineAttachments = ${inlineAttachments.length}');
if (listImgTag.isEmpty) { if (listImgTag.isEmpty) {
@@ -143,40 +143,45 @@ class HtmlAnalyzer {
final List<Future<void>> asyncTasks = []; final List<Future<void>> asyncTasks = [];
for (final imgTag in listImgTag) { for (final imgTag in listImgTag) {
late final LinkedHashMap<Object, String> attributes = imgTag.attributes; final attributes = imgTag.attributes;
final idImg = attributes['id'];
final imageSrc = attributes['src']; final imageSrc = attributes['src'];
if (imageSrc?.isEmpty ?? true) continue; if (imageSrc?.isEmpty ?? true) continue;
final idImg = attributes['id'];
if (idImg?.startsWith(cidPrefixKey) == true) { if (idImg?.startsWith(cidPrefixKey) == true) {
final cid = idImg!.substring(cidPrefixKey.length).trim(); final cid = idImg!.substring(cidPrefixKey.length).trim();
attributes['src'] = '$cidPrefixKey$cid';
attributes.remove('id');
final attachment = inlineAttachments[cid]; final attachment = inlineAttachments[cid];
log('HtmlAnalyzer::replaceImageBase64ToImageCID:attachment = $attachment');
if (attachment != null) { if (attachment != null) {
attributes['src'] = '$cidPrefixKey$cid';
attributes.remove('id');
inlineAttachmentsSet.add(attachment.toEmailBodyPart(charset: Constant.base64Charset)); inlineAttachmentsSet.add(attachment.toEmailBodyPart(charset: Constant.base64Charset));
continue;
} }
continue;
} }
if (uploadUri == null) continue; if (uploadUri == null) continue;
final taskId = idImg?.startsWith(cidPrefixKey) == true
? idImg!.substring(cidPrefixKey.length)
: _uuid.v1();
asyncTasks.add(_retrieveAttachmentFromUpload( asyncTasks.add(_retrieveAttachmentFromUpload(
taskId: taskId,
uploadUri: uploadUri, uploadUri: uploadUri,
base64ImageTag: imageSrc!, base64ImageTag: imageSrc!,
).then((newAttachment) { ).then((attachmentRecord) {
if (newAttachment == null) return; if (attachmentRecord == null) return;
final newInlineAttachment = newAttachment.toAttachmentWithDisposition( final newInlineAttachment = attachmentRecord.$1.toAttachmentWithDisposition(
disposition: ContentDisposition.inline, disposition: ContentDisposition.inline,
cid: _uuid.v1(), cid: attachmentRecord.$2,
); );
final newCid = newInlineAttachment.cid;
inlineAttachments[newCid!] = newInlineAttachment; final newCid = newInlineAttachment.cid!;
inlineAttachments[newCid] = newInlineAttachment;
attributes['src'] = '$cidPrefixKey$newCid'; attributes['src'] = '$cidPrefixKey$newCid';
attributes.remove('id');
inlineAttachmentsSet.add(newInlineAttachment.toEmailBodyPart(charset: Constant.base64Charset)); inlineAttachmentsSet.add(newInlineAttachment.toEmailBodyPart(charset: Constant.base64Charset));
})); }));
@@ -186,8 +191,7 @@ class HtmlAnalyzer {
await Future.wait(asyncTasks); await Future.wait(asyncTasks);
} }
final newContent = document.body?.innerHtml ?? emailContent; return Tuple2(document.body?.innerHtml ?? emailContent, inlineAttachmentsSet);
return Tuple2(newContent, inlineAttachmentsSet);
} }
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}) async { Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}) async {
@@ -210,7 +214,8 @@ class HtmlAnalyzer {
return newContent; return newContent;
} }
Future<Attachment?> _retrieveAttachmentFromUpload({ Future<(Attachment attachment, String taskId)?> _retrieveAttachmentFromUpload({
required String taskId,
required Uri uploadUri, required Uri uploadUri,
required String base64ImageTag, required String base64ImageTag,
}) async { }) async {
@@ -218,20 +223,19 @@ class HtmlAnalyzer {
final imageBytes = StringConvert.convertBase64ImageTagToBytes(base64ImageTag); final imageBytes = StringConvert.convertBase64ImageTagToBytes(base64ImageTag);
final mediaType = StringConvert.getMediaTypeFromBase64ImageTag(base64ImageTag); final mediaType = StringConvert.getMediaTypeFromBase64ImageTag(base64ImageTag);
log('HtmlAnalyzer::_retrieveAttachmentFromUpload: mimeType = ${mediaType?.mimeType} | imageBytesLength = ${imageBytes.length}'); log('HtmlAnalyzer::_retrieveAttachmentFromUpload: mimeType = ${mediaType?.mimeType} | imageBytesLength = ${imageBytes.length}');
final generateId = _uuid.v1();
final fileInfo = FileInfo.fromBytes( final fileInfo = FileInfo.fromBytes(
bytes: imageBytes, bytes: imageBytes,
name: '$generateId.${mediaType?.subtype ?? 'png'}', name: '$taskId.${mediaType?.subtype ?? 'png'}',
type: mediaType?.mimeType, type: mediaType?.mimeType,
); );
final attachment = await _fileUploader.uploadAttachment( final attachment = await _fileUploader.uploadAttachment(
UploadTaskId(generateId), UploadTaskId(taskId),
fileInfo, fileInfo,
uploadUri, uploadUri,
); );
log('HtmlAnalyzer::_retrieveAttachmentFromUpload:Attachment = $attachment'); log('HtmlAnalyzer::_retrieveAttachmentFromUpload:Attachment = $attachment | taskId = $taskId');
return attachment; return (attachment, taskId);
} catch (e) { } catch (e) {
logError('HtmlAnalyzer::_retrieveAttachmentFromUpload:Exception = $e'); logError('HtmlAnalyzer::_retrieveAttachmentFromUpload:Exception = $e');
return null; return null;
@@ -73,6 +73,37 @@ void main() {
expect(result.value2.first.cid, 'test123'); 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 = '<img id="cid:test123" src="data:image/png;base64,iVBORw0KGgo=">';
final inlineAttachments = <String, Attachment>{};
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( test(
'When HTML content has a base64 image and upload succeeds,\n' 'When HTML content has a base64 image and upload succeeds,\n'
'should replace src with new CID and add attachment to result', 'should replace src with new CID and add attachment to result',
@@ -101,6 +132,7 @@ void main() {
expect(imgTag?.attributes['src'], 'cid:new-uuid-123'); expect(imgTag?.attributes['src'], 'cid:new-uuid-123');
expect(result.value2.length, 1); expect(result.value2.length, 1);
expect(result.value2.first.cid, 'new-uuid-123'); expect(result.value2.first.cid, 'new-uuid-123');
verify(mockUuid.v1()).called(1);
verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(1); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(1);
}); });
@@ -151,9 +183,10 @@ void main() {
final document = parse(result.value1); final document = parse(result.value1);
final imgTags = document.querySelectorAll('img'); final imgTags = document.querySelectorAll('img');
expect(imgTags.length, 2); expect(imgTags.length, 2);
expect(imgTags[0].attributes['src'], 'cid:uuid1'); expect(imgTags[0].attributes['src'], contains('cid:'));
expect(imgTags[1].attributes['src'], 'cid:uuid1'); expect(imgTags[1].attributes['src'], contains('cid:'));
expect(result.value2.length, 2); expect(result.value2.length, 2);
verify(mockUuid.v1()).called(2);
verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2);
}); });
@@ -198,6 +231,7 @@ void main() {
expect(imgTags[1].attributes['src'], 'data:image/jpeg;base64,/9j/4AAQSkZJRg=='); expect(imgTags[1].attributes['src'], 'data:image/jpeg;base64,/9j/4AAQSkZJRg==');
expect(result.value2.length, 1); expect(result.value2.length, 1);
expect(result.value2.first.cid, 'uuid1'); expect(result.value2.first.cid, 'uuid1');
verify(mockUuid.v1()).called(2);
verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2);
}); });
@@ -229,6 +263,7 @@ void main() {
expect(imgTags[0].attributes['src'], 'data:image/png;base64,iVBORw0KGgo='); expect(imgTags[0].attributes['src'], 'data:image/png;base64,iVBORw0KGgo=');
expect(imgTags[1].attributes['src'], 'data:image/jpeg;base64,/9j/4AAQSkZJRg=='); expect(imgTags[1].attributes['src'], 'data:image/jpeg;base64,/9j/4AAQSkZJRg==');
expect(result.value2.length, 0); expect(result.value2.length, 0);
verify(mockUuid.v1()).called(2);
verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2); verify(mockFileUploader.uploadAttachment(any, any, uploadUri)).called(2);
}); });
@@ -256,6 +291,7 @@ void main() {
expect(imgTags.length, 1); expect(imgTags.length, 1);
expect(imgTags[0].attributes['src'], 'data:image/png;base64,invalid-base64-data'); expect(imgTags[0].attributes['src'], 'data:image/png;base64,invalid-base64-data');
expect(result.value2.length, 0); expect(result.value2.length, 0);
verify(mockUuid.v1()).called(1);
}); });
test( test(
@@ -282,6 +318,7 @@ void main() {
expect(imgTags.length, 1); expect(imgTags.length, 1);
expect(imgTags[0].attributes['src'], 'data:image/png;base64,iVBORw0KGgo='); expect(imgTags[0].attributes['src'], 'data:image/png;base64,iVBORw0KGgo=');
expect(result.value2.length, 0); expect(result.value2.length, 0);
verify(mockUuid.v1()).called(1);
verify(mockFileUploader.uploadAttachment(any, any, any)).called(1); verify(mockFileUploader.uploadAttachment(any, any, any)).called(1);
}); });
}); });