From 34cc7abe9bb70c7465af18120aad20acc33f976a Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 27 May 2025 03:27:05 +0700 Subject: [PATCH] TF-3752 Add more test case for SetForwardMethod unit test Signed-off-by: dab246 --- .../test/forward/set_forward_method_test.dart | 795 ++++++++++++++++++ 1 file changed, 795 insertions(+) diff --git a/forward/test/forward/set_forward_method_test.dart b/forward/test/forward/set_forward_method_test.dart index 9a3010a73..0162e1bbb 100644 --- a/forward/test/forward/set_forward_method_test.dart +++ b/forward/test/forward/set_forward_method_test.dart @@ -4,11 +4,18 @@ import 'package:forward/forward/forward_id.dart'; import 'package:forward/forward/get/get_forward_method.dart'; import 'package:forward/forward/get/get_forward_response.dart'; import 'package:forward/forward/set/set_forward_method.dart'; +import 'package:forward/forward/set/set_forward_response.dart'; import 'package:forward/forward/tmail_forward.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; +import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart'; +import 'package:jmap_dart_client/jmap/core/error/method/exception/error_method_response_exception.dart'; +import 'package:jmap_dart_client/jmap/core/error/set_error.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/request/request_invocation.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/jmap_request.dart'; void main() { @@ -135,4 +142,792 @@ void main() { expect(getForwardResponse.list, containsAll({expectedUpdated})); }); }); + + group('set forward method test:', () { + const String baseUrl = 'http://domain.com/jmap'; + + late final Dio dio; + late final DioAdapter dioAdapter; + + final methodCallId = MethodCallId('c0'); + final bobAccountId = AccountId(Id('bob')); + final sessionState = State('sessionState'); + final newState = State('newState'); + final tmailForward = TMailForward( + id: ForwardIdSingleton.forwardIdSingleton, + localCopy: true, + forwards: {'targetA@domain.org', 'targetB@domain.org'}, + ); + + setUpAll(() { + dio = Dio(BaseOptions(method: 'POST'))..options.baseUrl = baseUrl; + dioAdapter = DioAdapter(dio: dio); + }); + + test('Should fail when wrong account id', () async { + // Arrange + final unknownAccountId = AccountId(Id('unknownAccountId')); + final setForwardMethod = SetForwardMethod(unknownAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id : tmailForward + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "error", + { + "type": "accountNotFound", + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": unknownAccountId.id.value, + "update": {tmailForward.id!.id.value: tmailForward.toJson()} + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + // Assert + expect( + () => responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ), + throwsA(ErrorMethodResponseException(AccountNotFoundMethodResponse())), + ); + }); + + test('Should return unknown method when missing one capability', () async { + // Arrange + final listCapabilitiesUsed = {CapabilityIdentifier.jmapCore}; + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id : tmailForward + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "error", + { + "type": "unknownMethod", + "description": + "Missing capability(ies): com:linagora:params:jmap:forward" + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": listCapabilitiesUsed + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": {tmailForward.id!.id.value: tmailForward.toJson()} + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(listCapabilitiesUsed)) + .build() + .execute(); + + // Assert + expect( + () => responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ), + throwsA(ErrorMethodResponseException(UnknownMethodResponse( + description: + 'Missing capability(ies): com:linagora:params:jmap:forward', + ))), + ); + }); + + test('Should return unknown method when missing all capabilities', + () async { + // Arrange + final listCapabilitiesUsed = {}; + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id : tmailForward + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "error", + { + "type": "unknownMethod", + "description": + "Missing capability(ies): urn:ietf:params:jmap:core, com:linagora:params:jmap:forward" + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": listCapabilitiesUsed + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": {tmailForward.id!.id.value: tmailForward.toJson()} + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(listCapabilitiesUsed)) + .build() + .execute(); + + // Assert + expect( + () => responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ), + throwsA(ErrorMethodResponseException(UnknownMethodResponse( + description: + 'Missing capability(ies): urn:ietf:params:jmap:core, com:linagora:params:jmap:forward', + ))), + ); + }); + + test('Should return success', () async { + // Arrange + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id : tmailForward + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "Forward/set", + { + "accountId": bobAccountId.id.value, + "newState": newState.value, + "updated": {tmailForward.id!.id.value: {}} + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": {tmailForward.id!.id.value: tmailForward.toJson()} + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + final setForwardResponse = responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ); + + // Assert + expect( + setForwardResponse?.updated, + isNotEmpty, + ); + expect( + setForwardResponse?.notUpdated, + isNull, + ); + expect( + setForwardResponse?.notCreated, + isNull, + ); + expect( + setForwardResponse?.notDestroyed, + isNull, + ); + }); + + test('Should fail when missing forwards', () async { + // Arrange + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id : TMailForward( + localCopy: true, + ) + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "Forward/set", + { + "accountId": bobAccountId.id.value, + "newState": newState.value, + "notUpdated": { + tmailForward.id!.id.value: { + "type": "invalidArguments", + "description": "Missing '/forwards' property" + } + } + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": { + tmailForward.id!.id.value: {"localCopy": true} + } + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + final setForwardResponse = responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ); + + // Assert + expect( + setForwardResponse?.updated, + isNull, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.type, + SetError.invalidArguments, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.description, + 'Missing \'/forwards\' property', + ); + }); + + test('Should fail when missing localCopy', () async { + // Arrange + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id : TMailForward( + forwards: {}, + ) + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "Forward/set", + { + "accountId": bobAccountId.id.value, + "newState": newState.value, + "notUpdated": { + tmailForward.id!.id.value: { + "type": "invalidArguments", + "description": "Missing '/localCopy' property" + } + } + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": { + tmailForward.id!.id.value: {"forwards": []} + } + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + final setForwardResponse = responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ); + + // Assert + expect( + setForwardResponse?.updated, + isNull, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.type, + SetError.invalidArguments, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.description, + 'Missing \'/localCopy\' property', + ); + }); + + test('Should fail when invalid key', () async { + // Arrange + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({ + Id('invalidKey'): tmailForward + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "Forward/set", + { + "accountId": bobAccountId.id.value, + "newState": newState.value, + "notUpdated": { + "invalidKey": { + "type": "invalidArguments", + "description": "id invalidKey must be singleton" + } + } + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": { + "invalidKey": tmailForward.toJson(), + } + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + final setForwardResponse = responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ); + + // Assert + expect( + setForwardResponse?.updated, + isNull, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.type, + SetError.invalidArguments, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.description, + 'id invalidKey must be singleton', + ); + }); + + test('Should fail when invalid forwards', () async { + // Arrange + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id: tmailForward.copyWith( + forwards: {"123\$#%\$#invalid"} + ) + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "Forward/set", + { + "accountId": bobAccountId.id.value, + "newState": newState.value, + "notUpdated": { + tmailForward.id!.id.value: { + "type": "invalidArguments", + "description": + "'/forwards(0)' property is not valid: Invalid mailAddress: Out of data at position 1 in '123\$\$#%\$\$#invalid'" + } + } + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": { + tmailForward.id!.id.value: tmailForward.copyWith( + forwards: {"123\$#%\$#invalid"} + ).toJson(), + } + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + final setForwardResponse = responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ); + + // Assert + expect( + setForwardResponse?.updated, + isNull, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.type, + SetError.invalidArguments, + ); + expect( + setForwardResponse?.notUpdated?.values.firstOrNull?.description, + '\'/forwards(0)\' property is not valid: Invalid mailAddress: Out of data at position 1 in \'123\$\$#%\$\$#invalid\'', + ); + }); + + test('Should noop when empty map', () async { + // Arrange + final setForwardMethod = SetForwardMethod(bobAccountId) + ..addUpdatesSingleton({}); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "Forward/set", + { + "accountId": bobAccountId.id.value, + "newState": newState.value + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": bobAccountId.id.value, + "update": {}, + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + final setForwardResponse = responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ); + + // Assert + expect( + setForwardResponse?.updated, + isNull, + ); + expect( + setForwardResponse?.notUpdated, + isNull, + ); + }); + + test('Should reject from delegated account', () async { + // Arrange + final aliceAccountId = AccountId(Id('Alice')); + final setForwardMethod = SetForwardMethod(aliceAccountId) + ..addUpdatesSingleton({ + tmailForward.id!.id: tmailForward, + }); + final requestBuilder = JmapRequestBuilder( + HttpClient(dio), + ProcessingInvocation(), + ); + final invocation = requestBuilder.invocation( + setForwardMethod, + methodCallId: methodCallId, + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "error", + { + "type": "forbidden", + "description": + "Access to other accounts settings is forbidden" + }, + methodCallId.value, + ], + ], + }, + ), + data: { + "using": setForwardMethod.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + setForwardMethod.methodName.value, + { + "accountId": aliceAccountId.id.value, + "update": { + tmailForward.id!.id.value: tmailForward.toJson(), + }, + }, + methodCallId.value, + ] + ] + }, + ); + + // Act + final responseObject = await (requestBuilder + ..usings(setForwardMethod.requiredCapabilities)) + .build() + .execute(); + + // Assert + expect( + () => responseObject.parse( + invocation.methodCallId, + SetForwardResponse.deserialize, + ), + throwsA(ErrorMethodResponseException(ForbiddenMethodResponse( + description: 'Access to other accounts settings is forbidden'))), + ); + }); + }); } \ No newline at end of file