From f89bfaca742505254461dc0d340d3ff795c3e84f Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 27 Nov 2025 16:48:40 +0700 Subject: [PATCH] Ignore cache if it failed and write unit test for `forceEmailQuery = true` (cherry picked from commit 05496806823b6daed2b0abd889915a5fd7d5f88f) --- .../repository/thread_repository_impl.dart | 48 ++- ...get_emails_in_mailbox_interactor_test.dart | 334 ++++++++++++++++++ 2 files changed, 367 insertions(+), 15 deletions(-) diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 4be84d503..51eec34a1 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -159,21 +159,39 @@ class ThreadRepositoryImpl extends ThreadRepository { return; } - // Load cached emails + cached state - final cachedList = await localDataSource.getAllEmailCache( - accountId, - session.username, - inMailboxId: emailFilter?.mailboxId, - sort: sort, - limit: limit, - filterOption: emailFilter?.filterOption, - ); + List cachedList = const []; + jmap.State? cachedState; - final cachedState = await stateDataSource.getState( - accountId, - session.username, - StateType.email, - ); + // Load cached emails + try { + cachedList = await localDataSource.getAllEmailCache( + accountId, + session.username, + inMailboxId: emailFilter?.mailboxId, + sort: sort, + limit: limit, + filterOption: emailFilter?.filterOption, + ); + } catch (_) { + logError( + 'ThreadRepositoryImpl::forceQueryAllEmailsForWeb(): ' + 'Failed to load cached emails', + ); + } + + // Load cached state + try { + cachedState = await stateDataSource.getState( + accountId, + session.username, + StateType.email, + ); + } catch (_) { + logError( + 'ThreadRepositoryImpl::forceQueryAllEmailsForWeb(): ' + 'Failed to load cached email state', + ); + } final localResponse = EmailsResponse( emailList: cachedList, @@ -211,7 +229,7 @@ class ThreadRepositoryImpl extends ThreadRepository { // Combine server list + keep existing state yield EmailsResponse( emailList: serverResponse.emailList, - state: cachedState, + state: cachedState ?? serverResponse.state, ); } diff --git a/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart b/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart index 041a98873..fa0484d5d 100644 --- a/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart +++ b/test/features/thread/domain/usecases/get_emails_in_mailbox_interactor_test.dart @@ -103,5 +103,339 @@ void main() { ) })); }); + + test('should use forceQueryAllEmailsForWeb when forceEmailQuery = true', + () async { + final sort = { + EmailComparator(EmailComparatorProperty.sentAt)..setIsAscending(false), + }; + + final emailFilter = EmailFilter( + filter: EmailFilterCondition( + inMailbox: MailboxFixtures.inboxMailbox.id, + ), + mailboxId: MailboxFixtures.inboxMailbox.id, + ); + + when(threadRepository.forceQueryAllEmailsForWeb( + session: SessionFixtures.aliceSession, + accountId: AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + )).thenAnswer( + (_) => Stream.fromIterable([ + EmailsResponse( + emailList: [ + EmailFixtures.email1, + ], + state: jmap.State('s_cached'), + ), + EmailsResponse( + emailList: [ + EmailFixtures.email1, + EmailFixtures.email2, + EmailFixtures.email3, + ], + state: jmap.State('s_cached'), + ), + ]), + ); + + final resultStream = getEmailsInMailboxInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + forceEmailQuery: true, + ); + + final states = await resultStream.toList(); + + expect(states.length, 3); + + expect(states[0], Right(GetAllEmailLoading())); + + expect( + states[1], + Right( + GetAllEmailSuccess( + emailList: [ + EmailFixtures.email1.toPresentationEmail(), + ], + currentEmailState: jmap.State('s_cached'), + currentMailboxId: MailboxFixtures.inboxMailbox.id, + ), + ), + ); + + expect( + states[2], + Right( + GetAllEmailSuccess( + emailList: [ + EmailFixtures.email1.toPresentationEmail(), + EmailFixtures.email2.toPresentationEmail(), + EmailFixtures.email3.toPresentationEmail(), + ], + currentEmailState: jmap.State('s_cached'), + currentMailboxId: MailboxFixtures.inboxMailbox.id, + ), + ), + ); + + verify(threadRepository.forceQueryAllEmailsForWeb( + session: SessionFixtures.aliceSession, + accountId: AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + )).called(1); + + verifyNever(threadRepository.getAllEmail( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + emailFilter: anyNamed('emailFilter'), + propertiesCreated: anyNamed('propertiesCreated'), + propertiesUpdated: anyNamed('propertiesUpdated'), + )); + }); + + test( + 'forceEmailQuery = true, should continue when cache throws error and still return server emails', + () async { + final sort = { + EmailComparator(EmailComparatorProperty.sentAt)..setIsAscending(false), + }; + + final emailFilter = EmailFilter( + filter: EmailFilterCondition( + inMailbox: MailboxFixtures.inboxMailbox.id, + ), + mailboxId: MailboxFixtures.inboxMailbox.id, + ); + + when(threadRepository.forceQueryAllEmailsForWeb( + session: SessionFixtures.aliceSession, + accountId: AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + )).thenAnswer( + (_) => Stream.fromIterable([ + const EmailsResponse( + emailList: null, + state: null, + ), + EmailsResponse( + emailList: [ + EmailFixtures.email1, + EmailFixtures.email2, + ], + state: jmap.State('server_1'), + ), + ]), + ); + + final resultStream = getEmailsInMailboxInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + forceEmailQuery: true, + ); + + final states = await resultStream.toList(); + + expect(states.length, 2 + 1); + + expect(states[0], Right(GetAllEmailLoading())); + + expect( + states[1], + Right( + GetAllEmailSuccess( + emailList: const [], + currentEmailState: null, + currentMailboxId: MailboxFixtures.inboxMailbox.id, + ), + ), + ); + + expect( + states[2], + Right( + GetAllEmailSuccess( + emailList: [ + EmailFixtures.email1.toPresentationEmail(), + EmailFixtures.email2.toPresentationEmail(), + ], + currentEmailState: jmap.State('server_1'), + currentMailboxId: MailboxFixtures.inboxMailbox.id, + ), + ), + ); + }); + + test( + 'forceEmailQuery = true, should emit Failure when forceQueryAllEmailsForWeb throws exception', + () async { + final sort = { + EmailComparator(EmailComparatorProperty.sentAt)..setIsAscending(false), + }; + + final emailFilter = EmailFilter( + filter: EmailFilterCondition( + inMailbox: MailboxFixtures.inboxMailbox.id, + ), + mailboxId: MailboxFixtures.inboxMailbox.id, + ); + + when(threadRepository.forceQueryAllEmailsForWeb( + session: SessionFixtures.aliceSession, + accountId: AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + )).thenThrow(Exception('server_failed')); + + final resultStream = getEmailsInMailboxInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + forceEmailQuery: true, + ); + + final states = await resultStream.toList(); + + expect(states[0], Right(GetAllEmailLoading())); + expect(states[1].isLeft(), true); + + states[1].fold( + (failure) => expect(failure, isA()), + (_) => fail('Should not produce success'), + ); + }); + + test( + 'forceEmailQuery = false, should call getAllEmail() and not call forceQueryAllEmailsForWeb()', + () async { + final sort = { + EmailComparator(EmailComparatorProperty.sentAt)..setIsAscending(false), + }; + + final emailFilter = EmailFilter( + filter: EmailFilterCondition( + inMailbox: MailboxFixtures.inboxMailbox.id, + ), + mailboxId: MailboxFixtures.inboxMailbox.id, + ); + + when(threadRepository.getAllEmail( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, + getLatestChanges: true, + )).thenAnswer( + (_) => Stream.fromIterable([ + EmailsResponse( + emailList: [ + EmailFixtures.email1, + ], + state: jmap.State('s1'), + ), + EmailsResponse( + emailList: [ + EmailFixtures.email1, + EmailFixtures.email2, + EmailFixtures.email3, + ], + state: jmap.State('s1'), + ), + ]), + ); + + final resultStream = getEmailsInMailboxInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, + forceEmailQuery: false, + ); + + final states = await resultStream.toList(); + + expect(states.length, 3); + + expect(states[0], Right(GetAllEmailLoading())); + + expect( + states[1], + Right( + GetAllEmailSuccess( + emailList: [ + EmailFixtures.email1.toPresentationEmail(), + ], + currentEmailState: jmap.State('s1'), + currentMailboxId: MailboxFixtures.inboxMailbox.id, + ), + ), + ); + + expect( + states[2], + Right( + GetAllEmailSuccess( + emailList: [ + EmailFixtures.email1.toPresentationEmail(), + EmailFixtures.email2.toPresentationEmail(), + EmailFixtures.email3.toPresentationEmail(), + ], + currentEmailState: jmap.State('s1'), + currentMailboxId: MailboxFixtures.inboxMailbox.id, + ), + ), + ); + + verify(threadRepository.getAllEmail( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + limit: UnsignedInt(20), + sort: sort, + emailFilter: emailFilter, + propertiesCreated: ThreadConstants.propertiesDefault, + propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, + getLatestChanges: true, + )).called(1); + + verifyNever(threadRepository.forceQueryAllEmailsForWeb( + session: SessionFixtures.aliceSession, + accountId: AccountFixtures.aliceAccountId, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + emailFilter: anyNamed('emailFilter'), + propertiesCreated: anyNamed('propertiesCreated'), + )); + }); }); } \ No newline at end of file