Add unit test for combineEmailCache method
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_database_steps.dart';
|
||||
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||
|
||||
class UpgradeHiveDatabaseStepsV16 extends UpgradeDatabaseSteps {
|
||||
class UpgradeHiveDatabaseStepsV17 extends UpgradeDatabaseSteps {
|
||||
|
||||
final CachingManager _cachingManager;
|
||||
|
||||
UpgradeHiveDatabaseStepsV16(this._cachingManager);
|
||||
UpgradeHiveDatabaseStepsV17(this._cachingManager);
|
||||
|
||||
@override
|
||||
Future<void> onUpgrade(int oldVersion, int newVersion) async {
|
||||
|
||||
@@ -13,10 +13,11 @@ import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_st
|
||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v14.dart';
|
||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v15.dart';
|
||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v16.dart';
|
||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v17.dart';
|
||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v7.dart';
|
||||
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||
import 'package:tmail_ui_user/features/caching/config/cache_version.dart';
|
||||
import 'package:tmail_ui_user/features/caching/config/fcm_isolate_nam_server.dart';
|
||||
import 'package:tmail_ui_user/features/caching/config/fcm_isolate_name_server.dart';
|
||||
import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart';
|
||||
import 'package:tmail_ui_user/features/home/data/model/session_hive_obj.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/local/encryption_key_cache_manager.dart';
|
||||
@@ -82,7 +83,7 @@ class HiveCacheConfig {
|
||||
await UpgradeHiveDatabaseStepsV14(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||
await UpgradeHiveDatabaseStepsV15(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||
await UpgradeHiveDatabaseStepsV16(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||
await UpgradeHiveDatabaseStepsV16(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||
await UpgradeHiveDatabaseStepsV17(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||
|
||||
if (oldVersion != newVersion) {
|
||||
await cachingManager.storeCacheVersion(newVersion);
|
||||
|
||||
@@ -15,7 +15,6 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart';
|
||||
@@ -39,10 +38,7 @@ class EmailUtils {
|
||||
|
||||
static Properties getPropertiesForEmailChangeMethod(Session session, AccountId accountId) {
|
||||
if (CapabilityIdentifier.jamesCalendarEvent.isSupported(session, accountId)) {
|
||||
return Properties({
|
||||
...ThreadConstants.propertiesUpdatedDefault.value,
|
||||
IndividualHeaderIdentifier.headerCalendarEvent.value,
|
||||
});
|
||||
return ThreadConstants.propertiesCalendarEvent;
|
||||
} else {
|
||||
return ThreadConstants.propertiesUpdatedDefault;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/sort/comparator.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/state.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
|
||||
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/user_name.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
@@ -171,6 +172,18 @@ class ThreadRepositoryImpl extends ThreadRepository {
|
||||
return networkEmailResponse;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<List<Email>?> combineEmailCache({
|
||||
List<Email>? emailUpdated,
|
||||
Properties? updatedProperties,
|
||||
List<Email>? emailCacheList,
|
||||
}) =>
|
||||
_combineEmailCache(
|
||||
emailUpdated: emailUpdated,
|
||||
updatedProperties: updatedProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
|
||||
Future<List<Email>?> _combineEmailCache({
|
||||
List<Email>? emailUpdated,
|
||||
Properties? updatedProperties,
|
||||
@@ -197,6 +210,13 @@ class ThreadRepositoryImpl extends ThreadRepository {
|
||||
return combinedEmails;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
({Email updatedEmail, Email? oldEmail}) combineUpdatedWithEmailInCache(
|
||||
Email updatedEmail,
|
||||
List<Email>? emailCacheList,
|
||||
) =>
|
||||
_combineUpdatedWithEmailInCache(updatedEmail, emailCacheList);
|
||||
|
||||
({Email updatedEmail, Email? oldEmail}) _combineUpdatedWithEmailInCache(
|
||||
Email updatedEmail,
|
||||
List<Email>? emailCacheList,
|
||||
@@ -227,16 +247,24 @@ class ThreadRepositoryImpl extends ThreadRepository {
|
||||
destroyed: newDestroyed);
|
||||
}
|
||||
|
||||
Future<void> _updateState(AccountId accountId, UserName userName, State newState) async {
|
||||
Future<void> _updateState(
|
||||
AccountId accountId,
|
||||
UserName userName,
|
||||
jmap.State newState,
|
||||
) async {
|
||||
log('ThreadRepositoryImpl::_updateState(): [MAIL] $newState');
|
||||
await stateDataSource.saveState(accountId, userName, newState.toStateCache(StateType.email));
|
||||
await stateDataSource.saveState(
|
||||
accountId,
|
||||
userName,
|
||||
newState.toStateCache(StateType.email),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<EmailsResponse> refreshChanges(
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
State currentState,
|
||||
jmap.State currentState,
|
||||
{
|
||||
Set<Comparator>? sort,
|
||||
EmailFilter? emailFilter,
|
||||
@@ -371,7 +399,7 @@ class ThreadRepositoryImpl extends ThreadRepository {
|
||||
Future<void> _synchronizeCacheWithChanges(
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
State currentState,
|
||||
jmap.State currentState,
|
||||
{
|
||||
Properties? propertiesCreated,
|
||||
Properties? propertiesUpdated,
|
||||
@@ -381,7 +409,7 @@ class ThreadRepositoryImpl extends ThreadRepository {
|
||||
|
||||
EmailChangeResponse? emailChangeResponse;
|
||||
bool hasMoreChanges = true;
|
||||
State? sinceState = currentState;
|
||||
jmap.State? sinceState = currentState;
|
||||
|
||||
while(hasMoreChanges && sinceState != null) {
|
||||
log('ThreadRepositoryImpl::_synchronizeCacheWithChanges(): sinceState = $sinceState');
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
import 'package:core/data/model/source_type/data_source_type.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:model/email/email_property.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/repository/thread_repository_impl.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
|
||||
|
||||
class MockThreadDataSource extends Mock implements ThreadDataSource {}
|
||||
|
||||
class MockStateDataSource extends Mock implements StateDataSource {}
|
||||
|
||||
void main() {
|
||||
late ThreadRepositoryImpl repository;
|
||||
late Map<DataSourceType, ThreadDataSource> mockDataSources;
|
||||
late MockStateDataSource mockStateDataSource;
|
||||
|
||||
Email createEmail({
|
||||
String? id,
|
||||
String? subject,
|
||||
Map<MailboxId, bool>? mailboxIds,
|
||||
Map<KeyWordIdentifier, bool>? keywords,
|
||||
Map<IndividualHeaderIdentifier, String?>? xPriorityHeader,
|
||||
}) {
|
||||
return Email(
|
||||
id: id != null ? EmailId(Id(id)) : null,
|
||||
subject: subject,
|
||||
mailboxIds: mailboxIds ?? {MailboxId(Id('inbox')): true},
|
||||
keywords: keywords ?? {KeyWordIdentifier.emailSeen: true},
|
||||
xPriorityHeader: xPriorityHeader,
|
||||
);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
mockDataSources = {
|
||||
DataSourceType.network: MockThreadDataSource(),
|
||||
DataSourceType.local: MockThreadDataSource(),
|
||||
};
|
||||
mockStateDataSource = MockStateDataSource();
|
||||
repository = ThreadRepositoryImpl(mockDataSources, mockStateDataSource);
|
||||
});
|
||||
|
||||
group('ThreadRepositoryImpl.combineEmailCache', () {
|
||||
final defaultProperties = ThreadConstants.propertiesDefault;
|
||||
final nonDefaultProperties =
|
||||
Properties({EmailProperty.id, EmailProperty.subject});
|
||||
|
||||
final email1 = createEmail(
|
||||
id: '1',
|
||||
subject: 'Original 1',
|
||||
keywords: {KeyWordIdentifier.emailFlagged: true},
|
||||
mailboxIds: {MailboxId(Id('inbox')): true},
|
||||
);
|
||||
|
||||
final email2 = createEmail(
|
||||
id: '2',
|
||||
subject: 'Original 2',
|
||||
keywords: {KeyWordIdentifier.emailSeen: true},
|
||||
mailboxIds: {MailboxId(Id('archive')): true},
|
||||
);
|
||||
|
||||
final emailCacheList = [email1, email2];
|
||||
|
||||
test('should return null for null emailUpdated', () async {
|
||||
expect(
|
||||
await repository.combineEmailCache(
|
||||
emailUpdated: null,
|
||||
updatedProperties: defaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return empty list for empty emailUpdated', () async {
|
||||
expect(
|
||||
await repository.combineEmailCache(
|
||||
emailUpdated: [],
|
||||
updatedProperties: defaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
),
|
||||
isEmpty,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return null for null updatedProperties', () async {
|
||||
expect(
|
||||
await repository.combineEmailCache(
|
||||
emailUpdated: [createEmail(id: '1')],
|
||||
updatedProperties: null,
|
||||
emailCacheList: emailCacheList,
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return identical list when properties are default', () async {
|
||||
final updated = [createEmail(id: '1', subject: 'Updated')];
|
||||
expect(
|
||||
await repository.combineEmailCache(
|
||||
emailUpdated: updated,
|
||||
updatedProperties: defaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
),
|
||||
equals(updated),
|
||||
);
|
||||
});
|
||||
|
||||
test('should merge partial updates for non-default properties', () async {
|
||||
final updated = [createEmail(id: '1', subject: 'New Subject')];
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: updated,
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
|
||||
expect(result, hasLength(1));
|
||||
expect(result![0].subject, 'New Subject'); // Updated
|
||||
expect(
|
||||
result[0].keywords,
|
||||
{KeyWordIdentifier.emailFlagged: true},
|
||||
); // From cache
|
||||
});
|
||||
|
||||
test('should skip emails not found in cache', () async {
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [createEmail(id: '99')],
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('should handle empty cache list', () async {
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [createEmail(id: '1')],
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: [],
|
||||
);
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('should handle null cache list', () async {
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [createEmail(id: '1')],
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: null,
|
||||
);
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('should handle email with null ID', () async {
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [createEmail(id: null)],
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('should preserve xPriorityHeader when not in updated properties',
|
||||
() async {
|
||||
final cached = createEmail(
|
||||
id: '1',
|
||||
xPriorityHeader: {
|
||||
IndividualHeaderIdentifier.xPriorityHeader: 'high',
|
||||
},
|
||||
);
|
||||
final updated = createEmail(id: '1', subject: 'Updated');
|
||||
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [updated],
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: [cached],
|
||||
);
|
||||
|
||||
expect(result![0].xPriorityHeader, equals(cached.xPriorityHeader));
|
||||
});
|
||||
|
||||
test('should update xPriorityHeader when in updated properties', () async {
|
||||
final newXPriorityHeader = {
|
||||
IndividualHeaderIdentifier.xPriorityHeader: 'low',
|
||||
};
|
||||
final updated = createEmail(
|
||||
id: '1',
|
||||
xPriorityHeader: newXPriorityHeader,
|
||||
);
|
||||
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [updated],
|
||||
updatedProperties: Properties({
|
||||
EmailProperty.id,
|
||||
IndividualHeaderIdentifier.xPriorityHeader.value,
|
||||
}),
|
||||
emailCacheList: [createEmail(id: '1')],
|
||||
);
|
||||
|
||||
expect(result![0].xPriorityHeader, equals(newXPriorityHeader));
|
||||
});
|
||||
|
||||
test('should handle multiple emails update', () async {
|
||||
final updated = [
|
||||
createEmail(id: '1', subject: 'Updated 1'),
|
||||
createEmail(id: '2', subject: 'Updated 2'),
|
||||
];
|
||||
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: updated,
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
|
||||
expect(result, hasLength(2));
|
||||
expect(result![0].subject, 'Updated 1');
|
||||
expect(result[1].subject, 'Updated 2');
|
||||
});
|
||||
|
||||
test('should handle mix of cached and non-cached emails', () async {
|
||||
final updated = [
|
||||
createEmail(id: '1'), // Exists in cache
|
||||
createEmail(id: '99'), // Not in cache
|
||||
createEmail(id: '2'), // Exists in cache
|
||||
];
|
||||
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: updated,
|
||||
updatedProperties: nonDefaultProperties,
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
|
||||
expect(result, hasLength(2));
|
||||
expect(result!.map((e) => e.id?.id.value), containsAll(['1', '2']));
|
||||
});
|
||||
|
||||
test('should handle empty properties set', () async {
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [createEmail(id: '1')],
|
||||
updatedProperties: Properties({}),
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
expect(result, hasLength(1));
|
||||
expect(result![0].subject, 'Original 1');
|
||||
});
|
||||
|
||||
test('should handle properties with only non-existing fields', () async {
|
||||
final result = await repository.combineEmailCache(
|
||||
emailUpdated: [createEmail(id: '1')],
|
||||
updatedProperties: Properties({'non_existing_field'}),
|
||||
emailCacheList: emailCacheList,
|
||||
);
|
||||
expect(result, hasLength(1));
|
||||
expect(result![0].subject, 'Original 1');
|
||||
});
|
||||
});
|
||||
|
||||
group('ThreadRepositoryImpl.combineUpdatedWithEmailInCache', () {
|
||||
final cache = [
|
||||
createEmail(id: '1'),
|
||||
createEmail(id: '2'),
|
||||
];
|
||||
|
||||
test('should find existing email in cache', () {
|
||||
final result = repository.combineUpdatedWithEmailInCache(
|
||||
createEmail(id: '1'),
|
||||
cache,
|
||||
);
|
||||
expect(result.oldEmail, isNotNull);
|
||||
expect(result.updatedEmail.id?.id.value, '1');
|
||||
});
|
||||
|
||||
test('should return null for non-existing email', () {
|
||||
final result = repository.combineUpdatedWithEmailInCache(
|
||||
createEmail(id: '99'),
|
||||
cache,
|
||||
);
|
||||
expect(result.oldEmail, isNull);
|
||||
});
|
||||
|
||||
test('should return null for null email ID', () {
|
||||
final result = repository.combineUpdatedWithEmailInCache(
|
||||
createEmail(id: null),
|
||||
cache,
|
||||
);
|
||||
expect(result.oldEmail, isNull);
|
||||
});
|
||||
|
||||
test('should return null for empty cache', () {
|
||||
final result = repository.combineUpdatedWithEmailInCache(
|
||||
createEmail(id: '1'),
|
||||
[],
|
||||
);
|
||||
expect(result.oldEmail, isNull);
|
||||
});
|
||||
|
||||
test('should return null for null cache', () {
|
||||
final result = repository.combineUpdatedWithEmailInCache(
|
||||
createEmail(id: '1'),
|
||||
null,
|
||||
);
|
||||
expect(result.oldEmail, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user