TF-3719 Write unit test for _updateEmailCache in ThreadRepositoryImplement and getChanges in ThreadAPI
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -6,14 +6,19 @@ import 'package:jmap_dart_client/jmap/account_id.dart';
|
|||||||
import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart';
|
import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
|
import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/id.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/core/state.dart';
|
import 'package:jmap_dart_client/jmap/core/state.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/query/query_email_response.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/query/query_email_response.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||||
|
import 'package:model/extensions/account_id_extensions.dart';
|
||||||
import 'package:model/extensions/email_id_extensions.dart';
|
import 'package:model/extensions/email_id_extensions.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart';
|
import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart';
|
import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart';
|
import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart';
|
||||||
@@ -49,12 +54,17 @@ class MockQueryEmailResponse extends QueryEmailResponse {
|
|||||||
void main() {
|
void main() {
|
||||||
final baseOption = BaseOptions(method: 'POST');
|
final baseOption = BaseOptions(method: 'POST');
|
||||||
final dio = Dio(baseOption)..options.baseUrl = 'http://domain.com/jmap';
|
final dio = Dio(baseOption)..options.baseUrl = 'http://domain.com/jmap';
|
||||||
|
final dioAdapterHeaders = <String, dynamic>{
|
||||||
|
'accept': 'application/json;jmapVersion=rfc-8621',
|
||||||
|
};
|
||||||
final dioAdapter = DioAdapter(dio: dio);
|
final dioAdapter = DioAdapter(dio: dio);
|
||||||
final httpClient = HttpClient(dio);
|
final httpClient = HttpClient(dio);
|
||||||
final threadApi = ThreadAPI(httpClient);
|
final threadApi = ThreadAPI(httpClient);
|
||||||
|
|
||||||
final sessionState = State('some-session-state');
|
final sessionState = State('some-session-state');
|
||||||
final state = State('some-state');
|
final state = State('some-state');
|
||||||
|
final sinceState = State('since-state');
|
||||||
|
final newState = State('new-state');
|
||||||
final filter = EmailFilterCondition(text: 'some-text');
|
final filter = EmailFilterCondition(text: 'some-text');
|
||||||
final queryState = State('some-query-state');
|
final queryState = State('some-query-state');
|
||||||
|
|
||||||
@@ -423,5 +433,347 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('getChanges:', () {
|
||||||
|
int emailSize = 10;
|
||||||
|
|
||||||
|
final createdEmailIds = List.generate(
|
||||||
|
emailSize,
|
||||||
|
(index) => EmailId(Id('created_$index')),
|
||||||
|
);
|
||||||
|
|
||||||
|
final updatedEmailIds = List.generate(
|
||||||
|
emailSize,
|
||||||
|
(index) => EmailId(Id('updated_$index')),
|
||||||
|
);
|
||||||
|
|
||||||
|
final destroyedEmailIds = List.generate(
|
||||||
|
emailSize,
|
||||||
|
(index) => EmailId(Id('destroyed_$index')),
|
||||||
|
);
|
||||||
|
|
||||||
|
final createdEmails = List.generate(
|
||||||
|
emailSize,
|
||||||
|
(index) => Email(
|
||||||
|
id: EmailId(Id('created_$index')),
|
||||||
|
subject: 'Subject $index',
|
||||||
|
preview: 'Preview $index',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final updatedEmails = List.generate(
|
||||||
|
emailSize,
|
||||||
|
(index) => Email(
|
||||||
|
id: EmailId(Id('updated_$index')),
|
||||||
|
keywords: {
|
||||||
|
KeyWordIdentifier.emailSeen: true,
|
||||||
|
},
|
||||||
|
mailboxIds: {
|
||||||
|
MailboxId(Id('inbox-id')): true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final defaultCreatedProperties = Properties({
|
||||||
|
'subject',
|
||||||
|
'preview',
|
||||||
|
});
|
||||||
|
|
||||||
|
final defaultUpdatedProperties = Properties({
|
||||||
|
'mailboxIds',
|
||||||
|
'keywords',
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> generateRequest({
|
||||||
|
Properties? propertiesCreated,
|
||||||
|
Properties? propertiesUpdated,
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
"using": [
|
||||||
|
"urn:ietf:params:jmap:core",
|
||||||
|
"urn:ietf:params:jmap:mail",
|
||||||
|
"urn:apache:james:params:jmap:mail:shares"
|
||||||
|
],
|
||||||
|
"methodCalls": [
|
||||||
|
[
|
||||||
|
"Email/changes",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"sinceState": sinceState.value,
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
if (propertiesUpdated != null)
|
||||||
|
[
|
||||||
|
"Email/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"#ids": {
|
||||||
|
"resultOf": "c0",
|
||||||
|
"name": "Email/changes",
|
||||||
|
"path": "/updated/*"
|
||||||
|
},
|
||||||
|
"properties": propertiesUpdated.value.toList()
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
],
|
||||||
|
if (propertiesCreated != null)
|
||||||
|
[
|
||||||
|
"Email/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"#ids": {
|
||||||
|
"resultOf": "c0",
|
||||||
|
"name": "Email/changes",
|
||||||
|
"path": "/created/*"
|
||||||
|
},
|
||||||
|
"properties": propertiesCreated.value.toList()
|
||||||
|
},
|
||||||
|
if (propertiesUpdated != null) "c2" else "c1"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
Map<String, dynamic> generateResponse({
|
||||||
|
Properties? propertiesCreated,
|
||||||
|
Properties? propertiesUpdated,
|
||||||
|
bool existNotFoundCreatedEmails = false,
|
||||||
|
bool existNotFoundUpdatedEmails = false,
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
"sessionState": sessionState.value,
|
||||||
|
"methodResponses": [
|
||||||
|
[
|
||||||
|
"Email/changes",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"oldState": sinceState.value,
|
||||||
|
"newState": newState.value,
|
||||||
|
"hasMoreChanges": false,
|
||||||
|
"created": createdEmailIds
|
||||||
|
.map((emailId) => emailId.id.value)
|
||||||
|
.toList(),
|
||||||
|
"updated": updatedEmailIds
|
||||||
|
.map((emailId) => emailId.id.value)
|
||||||
|
.toList(),
|
||||||
|
"destroyed": destroyedEmailIds
|
||||||
|
.map((emailId) => emailId.id.value)
|
||||||
|
.toList(),
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
if (propertiesUpdated != null)
|
||||||
|
[
|
||||||
|
"Email/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"state": newState.value,
|
||||||
|
"list": existNotFoundUpdatedEmails
|
||||||
|
? updatedEmails.map((email) => email.toJson()).toList().sublist(0, 5)
|
||||||
|
: updatedEmails.map((email) => email.toJson()).toList(),
|
||||||
|
"notFound": existNotFoundUpdatedEmails
|
||||||
|
? updatedEmailIds.map((emailId) => emailId.id.value).toList().sublist(5, 10)
|
||||||
|
: [],
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
],
|
||||||
|
if (propertiesCreated != null)
|
||||||
|
[
|
||||||
|
"Email/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"state": newState.value,
|
||||||
|
"list": existNotFoundUpdatedEmails
|
||||||
|
? createdEmails.map((email) => email.toJson()).toList().sublist(0, 5)
|
||||||
|
: createdEmails.map((email) => email.toJson()).toList(),
|
||||||
|
"notFound": existNotFoundUpdatedEmails
|
||||||
|
? createdEmailIds.map((emailId) => emailId.id.value).toList().sublist(5, 10)
|
||||||
|
: [],
|
||||||
|
},
|
||||||
|
if (propertiesUpdated != null) "c2" else "c1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should return created, updated, destroyed emails '
|
||||||
|
'when has both created and updated properties '
|
||||||
|
'and response Email/get has list', () async {
|
||||||
|
// arrange
|
||||||
|
dioAdapter.onPost(
|
||||||
|
'',
|
||||||
|
(server) => server.reply(
|
||||||
|
200,
|
||||||
|
generateResponse(
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
propertiesUpdated: defaultUpdatedProperties,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
data: generateRequest(
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
propertiesUpdated: defaultUpdatedProperties,
|
||||||
|
),
|
||||||
|
headers: dioAdapterHeaders..addAll({'content-length': 1245}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = await threadApi.getChanges(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
sinceState,
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
propertiesUpdated: defaultUpdatedProperties,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
EmailChangeResponse(
|
||||||
|
updated: updatedEmails,
|
||||||
|
created: createdEmails,
|
||||||
|
destroyed: destroyedEmailIds,
|
||||||
|
newStateChanges: newState,
|
||||||
|
newStateEmail: newState,
|
||||||
|
hasMoreChanges: false,
|
||||||
|
updatedProperties: defaultUpdatedProperties,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should return created, updated, destroyed emails '
|
||||||
|
'when has both created and updated properties '
|
||||||
|
'and response Email/get has list and not found emails', () async {
|
||||||
|
// arrange
|
||||||
|
dioAdapter.onPost(
|
||||||
|
'',
|
||||||
|
(server) => server.reply(
|
||||||
|
200,
|
||||||
|
generateResponse(
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
propertiesUpdated: defaultUpdatedProperties,
|
||||||
|
existNotFoundCreatedEmails: true,
|
||||||
|
existNotFoundUpdatedEmails: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
data: generateRequest(
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
propertiesUpdated: defaultUpdatedProperties,
|
||||||
|
),
|
||||||
|
headers: dioAdapterHeaders..addAll({'content-length': 1245}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = await threadApi.getChanges(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
sinceState,
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
propertiesUpdated: defaultUpdatedProperties,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
final newDestroyedEmailIds = destroyedEmailIds
|
||||||
|
+ updatedEmailIds.sublist(5, 10)
|
||||||
|
+ createdEmailIds.sublist(5, 10);
|
||||||
|
|
||||||
|
final newUpdatedEmails = updatedEmails.sublist(0, 5);
|
||||||
|
final newCreatedEmails = createdEmails.sublist(0, 5);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
EmailChangeResponse(
|
||||||
|
updated: newUpdatedEmails,
|
||||||
|
created: newCreatedEmails,
|
||||||
|
destroyed: newDestroyedEmailIds,
|
||||||
|
newStateChanges: newState,
|
||||||
|
newStateEmail: newState,
|
||||||
|
hasMoreChanges: false,
|
||||||
|
updatedProperties: defaultUpdatedProperties,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should return created, destroyed emails '
|
||||||
|
'when only has created properties '
|
||||||
|
'and response Email/get has list', () async {
|
||||||
|
// arrange
|
||||||
|
dioAdapter.onPost(
|
||||||
|
'',
|
||||||
|
(server) => server.reply(
|
||||||
|
200,
|
||||||
|
generateResponse(
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
data: generateRequest(
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
),
|
||||||
|
headers: dioAdapterHeaders..addAll({'content-length': 798}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = await threadApi.getChanges(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
sinceState,
|
||||||
|
propertiesCreated: defaultCreatedProperties,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
EmailChangeResponse(
|
||||||
|
created: createdEmails,
|
||||||
|
destroyed: destroyedEmailIds,
|
||||||
|
newStateChanges: newState,
|
||||||
|
newStateEmail: newState,
|
||||||
|
hasMoreChanges: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should only return destroyed emails '
|
||||||
|
'when both created, updated properties are absent '
|
||||||
|
'and response Email/changes has destroyed list', () async {
|
||||||
|
// arrange
|
||||||
|
dioAdapter.onPost(
|
||||||
|
'',
|
||||||
|
(server) => server.reply(
|
||||||
|
200,
|
||||||
|
generateResponse(),
|
||||||
|
),
|
||||||
|
data: generateRequest(),
|
||||||
|
headers: dioAdapterHeaders..addAll({'content-length': 355}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = await threadApi.getChanges(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
sinceState,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
equals(
|
||||||
|
EmailChangeResponse(
|
||||||
|
destroyed: destroyedEmailIds,
|
||||||
|
newStateChanges: newState,
|
||||||
|
hasMoreChanges: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.
|
|||||||
import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart';
|
import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart';
|
import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
|
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dart';
|
||||||
|
|
||||||
import '../../../../fixtures/account_fixtures.dart';
|
import '../../../../fixtures/account_fixtures.dart';
|
||||||
import '../../../../fixtures/session_fixtures.dart';
|
import '../../../../fixtures/session_fixtures.dart';
|
||||||
@@ -531,4 +532,480 @@ void main() {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('The functions regard to updateEmailCache:', () {
|
||||||
|
test(
|
||||||
|
'when local cache is empty should fetch from network '
|
||||||
|
'and network has mail not found '
|
||||||
|
'should delete mail not found in cache', () async {
|
||||||
|
// Arrange
|
||||||
|
when(threadDataSource.getAllEmailCache(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
filterOption: anyNamed('filterOption'),
|
||||||
|
inMailboxId: anyNamed('inMailboxId'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
)).thenAnswer((_) => Future.value([]));
|
||||||
|
|
||||||
|
when(stateDataSource.getState(any, any, any))
|
||||||
|
.thenAnswer((_) => Future.value(null));
|
||||||
|
|
||||||
|
final networkEmails = List.generate(
|
||||||
|
ThreadConstants.defaultLimit.value.toInt(),
|
||||||
|
(index) => Email(id: EmailId(Id('network_$index'))),
|
||||||
|
);
|
||||||
|
|
||||||
|
final networkNotFoundEmailIds = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => EmailId(Id('network_not_found_$index')),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
position: anyNamed('position'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
)).thenAnswer((_) => Future.value(EmailsResponse(
|
||||||
|
emailList: networkEmails,
|
||||||
|
notFoundEmailIds: networkNotFoundEmailIds,
|
||||||
|
state: State('network_state'),
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final responses = await threadRepository
|
||||||
|
.getAllEmail(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responses.length, 2);
|
||||||
|
expect(responses[0].emailList, networkEmails);
|
||||||
|
expect(responses[0].notFoundEmailIds, networkNotFoundEmailIds);
|
||||||
|
verify(threadDataSource.update(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
created: networkEmails,
|
||||||
|
destroyed: networkNotFoundEmailIds,
|
||||||
|
));
|
||||||
|
verify(stateDataSource.saveState(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'when filter option is not "all" '
|
||||||
|
'and no need getLatestChanges '
|
||||||
|
'then fetch first page of all messages for this folder '
|
||||||
|
'and network has mail not found '
|
||||||
|
'should delete mail not found in cache', () async {
|
||||||
|
// Arrange
|
||||||
|
final mailboxId = MailboxId(Id('mailbox_id'));
|
||||||
|
final localEmails = List.generate(
|
||||||
|
10,
|
||||||
|
(index) => Email(id: EmailId(Id('local_$index'))),
|
||||||
|
);
|
||||||
|
when(threadDataSource.getAllEmailCache(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
filterOption: anyNamed('filterOption'),
|
||||||
|
inMailboxId: anyNamed('inMailboxId'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
)).thenAnswer((_) => Future.value(localEmails));
|
||||||
|
|
||||||
|
when(stateDataSource.getState(any, any, any))
|
||||||
|
.thenAnswer((_) => Future.value(State('local_state')));
|
||||||
|
|
||||||
|
final networkEmails = List.generate(
|
||||||
|
ThreadConstants.defaultLimit.value.toInt(),
|
||||||
|
(index) => Email(id: EmailId(Id('network_$index'))),
|
||||||
|
);
|
||||||
|
|
||||||
|
final networkNotFoundEmailIds = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => EmailId(Id('network_not_found_$index')),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
position: anyNamed('position'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
)).thenAnswer((_) => Future.value(EmailsResponse(
|
||||||
|
emailList: networkEmails,
|
||||||
|
notFoundEmailIds: networkNotFoundEmailIds,
|
||||||
|
state: State('network_state'),
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final responses = await threadRepository
|
||||||
|
.getAllEmail(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
emailFilter: EmailFilter(
|
||||||
|
filterOption: FilterMessageOption.unread,
|
||||||
|
mailboxId: mailboxId,
|
||||||
|
),
|
||||||
|
getLatestChanges: false,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responses.length, 2);
|
||||||
|
verify(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
)).called(2); // Once for initial fetch, once for first page
|
||||||
|
verify(threadDataSource.update(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
created: networkEmails,
|
||||||
|
destroyed: networkNotFoundEmailIds,
|
||||||
|
)).called(2);
|
||||||
|
verifyNever(stateDataSource.saveState(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'when local has mail in cache and getLatestChanges is true '
|
||||||
|
'and email change has destroyed mail '
|
||||||
|
'should delete destroyed mail in cache', () async {
|
||||||
|
// Arrange
|
||||||
|
final localEmails = List.generate(
|
||||||
|
ThreadConstants.defaultLimit.value.toInt(),
|
||||||
|
(index) => Email(id: EmailId(Id('local_$index'))),
|
||||||
|
);
|
||||||
|
when(threadDataSource.getAllEmailCache(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
filterOption: anyNamed('filterOption'),
|
||||||
|
inMailboxId: anyNamed('inMailboxId'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
)).thenAnswer((_) => Future.value(localEmails));
|
||||||
|
|
||||||
|
when(stateDataSource.getState(any, any, any))
|
||||||
|
.thenAnswer((_) => Future.value(State('local_state')));
|
||||||
|
|
||||||
|
final changedEmails = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => Email(id: EmailId(Id('changed_$index'))),
|
||||||
|
);
|
||||||
|
|
||||||
|
final destroyedEmailIds = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => EmailId(Id('destroyed_mail_$index')),
|
||||||
|
);
|
||||||
|
when(threadDataSource.getChanges(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
propertiesCreated: anyNamed('propertiesCreated'),
|
||||||
|
propertiesUpdated: anyNamed('propertiesUpdated'),
|
||||||
|
)).thenAnswer((_) => Future.value(EmailChangeResponse(
|
||||||
|
hasMoreChanges: false,
|
||||||
|
created: changedEmails,
|
||||||
|
destroyed: destroyedEmailIds,
|
||||||
|
newStateEmail: State('new_state'))));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final responses = await threadRepository
|
||||||
|
.getAllEmail(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
getLatestChanges: true,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responses.length, 2);
|
||||||
|
verifyNever(threadDataSource.getAllEmail(any, any));
|
||||||
|
verify(threadDataSource.getChanges(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
propertiesCreated: anyNamed('propertiesCreated'),
|
||||||
|
propertiesUpdated: anyNamed('propertiesUpdated'),
|
||||||
|
));
|
||||||
|
verify(threadDataSource.update(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
created: changedEmails,
|
||||||
|
destroyed: destroyedEmailIds,
|
||||||
|
));
|
||||||
|
verify(stateDataSource.saveState(any, any, any));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'when getChanges has more changes should fetch all changes '
|
||||||
|
'and first email change has destroyed mail '
|
||||||
|
'should delete destroyed mail in cache', () async {
|
||||||
|
// Arrange
|
||||||
|
final localEmails = List.generate(
|
||||||
|
ThreadConstants.defaultLimit.value.toInt(),
|
||||||
|
(index) => Email(id: EmailId(Id('local_$index'))),
|
||||||
|
);
|
||||||
|
when(threadDataSource.getAllEmailCache(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
filterOption: anyNamed('filterOption'),
|
||||||
|
inMailboxId: anyNamed('inMailboxId'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
)).thenAnswer((_) => Future.value(localEmails));
|
||||||
|
|
||||||
|
when(stateDataSource.getState(any, any, any))
|
||||||
|
.thenAnswer((_) => Future.value(State('local_state')));
|
||||||
|
|
||||||
|
final firstChanges = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => Email(id: EmailId(Id('change1_$index'))),
|
||||||
|
);
|
||||||
|
final secondChanges = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => Email(id: EmailId(Id('change2_$index'))),
|
||||||
|
);
|
||||||
|
|
||||||
|
var callCount = 0;
|
||||||
|
|
||||||
|
final firstDestroyedEmailIds = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => EmailId(Id('destroyed_mail_$index')),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(threadDataSource.getChanges(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
propertiesCreated: anyNamed('propertiesCreated'),
|
||||||
|
propertiesUpdated: anyNamed('propertiesUpdated'),
|
||||||
|
)).thenAnswer((_) {
|
||||||
|
callCount++;
|
||||||
|
if (callCount == 1) {
|
||||||
|
return Future.value(EmailChangeResponse(
|
||||||
|
hasMoreChanges: true,
|
||||||
|
created: firstChanges,
|
||||||
|
destroyed: firstDestroyedEmailIds,
|
||||||
|
newStateChanges: State('intermediate_state'),
|
||||||
|
newStateEmail: State('intermediate_state_email'),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Future.value(EmailChangeResponse(
|
||||||
|
hasMoreChanges: false,
|
||||||
|
created: secondChanges,
|
||||||
|
newStateChanges: State('final_state'),
|
||||||
|
newStateEmail: State('final_state_email'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final responses = await threadRepository
|
||||||
|
.getAllEmail(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
getLatestChanges: true,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responses.length, 2);
|
||||||
|
verify(threadDataSource.getChanges(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
propertiesCreated: anyNamed('propertiesCreated'),
|
||||||
|
propertiesUpdated: anyNamed('propertiesUpdated'),
|
||||||
|
)).called(2);
|
||||||
|
verify(threadDataSource.update(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
created: anyNamed('created'),
|
||||||
|
destroyed: anyNamed('destroyed'),
|
||||||
|
));
|
||||||
|
verify(stateDataSource.saveState(any, any, any));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'when local has mail in cache but network errors '
|
||||||
|
'and return email from local '
|
||||||
|
'should not call update email cache', () async {
|
||||||
|
// Arrange
|
||||||
|
final localEmails = List.generate(
|
||||||
|
ThreadConstants.defaultLimit.value.toInt(),
|
||||||
|
(index) => Email(id: EmailId(Id('local_$index'))),
|
||||||
|
);
|
||||||
|
when(threadDataSource.getAllEmailCache(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
filterOption: anyNamed('filterOption'),
|
||||||
|
inMailboxId: anyNamed('inMailboxId'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
)).thenAnswer((_) => Future.value(localEmails));
|
||||||
|
|
||||||
|
when(stateDataSource.getState(any, any, any))
|
||||||
|
.thenAnswer((_) => Future.value(State('local_state')));
|
||||||
|
|
||||||
|
when(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
position: anyNamed('position'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
)).thenThrow(Exception('Network error'));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final responses = await threadRepository
|
||||||
|
.getAllEmail(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
getLatestChanges: false,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responses.length, 2);
|
||||||
|
expect(responses[0].emailList, localEmails);
|
||||||
|
verifyNever(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
position: anyNamed('position'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
));
|
||||||
|
verifyNever(threadDataSource.update(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
created: anyNamed('created'),
|
||||||
|
updated: anyNamed('updated'),
|
||||||
|
destroyed: anyNamed('created'),
|
||||||
|
));
|
||||||
|
verifyNever(stateDataSource.saveState(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'when fetching more emails '
|
||||||
|
'and network has mails and not found mails '
|
||||||
|
'should delete mail not found in cache', () async {
|
||||||
|
// Arrange
|
||||||
|
final networkEmails = List.generate(
|
||||||
|
ThreadConstants.defaultLimit.value.toInt(),
|
||||||
|
(index) => Email(id: EmailId(Id('network_$index'))),
|
||||||
|
);
|
||||||
|
|
||||||
|
final networkNotFoundEmailIds = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => EmailId(Id('network_not_found_$index')),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
position: anyNamed('position'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
)).thenAnswer((_) => Future.value(EmailsResponse(
|
||||||
|
emailList: networkEmails,
|
||||||
|
notFoundEmailIds: networkNotFoundEmailIds,
|
||||||
|
state: State('network_state'),
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final responses = await threadRepository
|
||||||
|
.loadMoreEmails(GetEmailRequest(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(responses.length, 1);
|
||||||
|
expect(responses[0].emailList, networkEmails);
|
||||||
|
expect(responses[0].notFoundEmailIds, networkNotFoundEmailIds);
|
||||||
|
verify(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
position: anyNamed('position'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
));
|
||||||
|
verify(threadDataSource.update(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
created: networkEmails,
|
||||||
|
destroyed: networkNotFoundEmailIds,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'when fetching more emails and network errors '
|
||||||
|
'should not call update mail cache', () async {
|
||||||
|
// Arrange
|
||||||
|
when(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
position: anyNamed('position'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
)).thenThrow(Exception('Network error'));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expectLater(
|
||||||
|
() => threadRepository
|
||||||
|
.loadMoreEmails(GetEmailRequest(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
throwsA(isA<Exception>().having((e) => e.toString(), 'description', contains('Network error'))),
|
||||||
|
);
|
||||||
|
verifyNever(threadDataSource.getAllEmail(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
limit: anyNamed('limit'),
|
||||||
|
position: anyNamed('position'),
|
||||||
|
sort: anyNamed('sort'),
|
||||||
|
filter: anyNamed('filter'),
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
));
|
||||||
|
verifyNever(threadDataSource.update(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
created: anyNamed('created'),
|
||||||
|
updated: anyNamed('updated'),
|
||||||
|
destroyed: anyNamed('created'),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user