diff --git a/lib/jmap/core/method/request/calendar_event_reply_method.dart b/lib/jmap/core/method/request/calendar_event_reply_method.dart new file mode 100644 index 0000000..286faea --- /dev/null +++ b/lib/jmap/core/method/request/calendar_event_reply_method.dart @@ -0,0 +1,26 @@ +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/method/method.dart'; +import 'package:json_annotation/json_annotation.dart'; + +abstract class CalendarEventReplyMethod extends MethodRequiringAccountId + with OptionalLanguage { + CalendarEventReplyMethod( + super.accountId, + { + required this.blobIds, + }); + + final List blobIds; + + @override + List get props => [accountId, blobIds, language]; +} + +mixin OptionalLanguage { + @JsonKey(includeIfNull: false) + String? language; + + void addLanguage(String value) { + language = value; + } +} \ No newline at end of file diff --git a/lib/jmap/core/method/response/calendar_event_reply_response.dart b/lib/jmap/core/method/response/calendar_event_reply_response.dart new file mode 100644 index 0000000..dd206ee --- /dev/null +++ b/lib/jmap/core/method/response/calendar_event_reply_response.dart @@ -0,0 +1,11 @@ +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/method/method_response.dart'; + +abstract class CalendarEventReplyResponse extends ResponseRequiringAccountId { + CalendarEventReplyResponse(super.accountId, this.notFound); + + final List? notFound; + + @override + List get props => [accountId, notFound]; +} \ No newline at end of file diff --git a/lib/jmap/mail/calendar/reply/calendar_event_accept_method.dart b/lib/jmap/mail/calendar/reply/calendar_event_accept_method.dart new file mode 100644 index 0000000..cd35a05 --- /dev/null +++ b/lib/jmap/mail/calendar/reply/calendar_event_accept_method.dart @@ -0,0 +1,28 @@ +import 'package:jmap_dart_client/http/converter/account_id_converter.dart'; +import 'package:jmap_dart_client/http/converter/id_converter.dart'; +import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; +import 'package:jmap_dart_client/jmap/core/method/request/calendar_event_reply_method.dart'; +import 'package:jmap_dart_client/jmap/core/request/request_invocation.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'calendar_event_accept_method.g.dart'; + +@JsonSerializable(converters: [ + AccountIdConverter(), + IdConverter(), +]) +class CalendarEventAcceptMethod extends CalendarEventReplyMethod { + CalendarEventAcceptMethod(super.accountId, {required super.blobIds}); + + @override + MethodName get methodName => MethodName('CalendarEvent/accept'); + + @override + Set get requiredCapabilities => { + CapabilityIdentifier.jmapCore, + CapabilityIdentifier.jamesCalendarEvent + }; + + @override + Map toJson() => _$CalendarEventAcceptMethodToJson(this); +} \ No newline at end of file diff --git a/lib/jmap/mail/calendar/reply/calendar_event_accept_method.g.dart b/lib/jmap/mail/calendar/reply/calendar_event_accept_method.g.dart new file mode 100644 index 0000000..7df081a --- /dev/null +++ b/lib/jmap/mail/calendar/reply/calendar_event_accept_method.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'calendar_event_accept_method.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CalendarEventAcceptMethod _$CalendarEventAcceptMethodFromJson( + Map json) => + CalendarEventAcceptMethod( + const AccountIdConverter().fromJson(json['accountId'] as String), + blobIds: (json['blobIds'] as List) + .map((e) => const IdConverter().fromJson(e as String)) + .toList(), + )..language = json['language'] as String?; + +Map _$CalendarEventAcceptMethodToJson( + CalendarEventAcceptMethod instance) { + final val = { + 'accountId': const AccountIdConverter().toJson(instance.accountId), + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('language', instance.language); + val['blobIds'] = instance.blobIds.map(const IdConverter().toJson).toList(); + return val; +} diff --git a/lib/jmap/mail/calendar/reply/calendar_event_accept_response.dart b/lib/jmap/mail/calendar/reply/calendar_event_accept_response.dart new file mode 100644 index 0000000..a621dd1 --- /dev/null +++ b/lib/jmap/mail/calendar/reply/calendar_event_accept_response.dart @@ -0,0 +1,29 @@ +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/method/response/calendar_event_reply_response.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_id.dart'; +import 'package:jmap_dart_client/util/json_parsers.dart'; + +class CalendarEventAcceptResponse extends CalendarEventReplyResponse { + CalendarEventAcceptResponse( + super.accountId, + super.notFound, + { + this.accepted, + this.notCreated + }); + + final List? accepted; + final List? notCreated; + + static CalendarEventAcceptResponse deserialize(Map json) { + return CalendarEventAcceptResponse( + JsonParsers().parsingAccountId(json), + JsonParsers().parsingListId(json, 'notFound'), + accepted: JsonParsers().parsingListEventId(json, 'accepted'), + notCreated: JsonParsers().parsingListId(json, 'notCreated'), + ); + } + + @override + List get props => [...super.props, accepted, notCreated]; +} \ No newline at end of file diff --git a/lib/util/json_parsers.dart b/lib/util/json_parsers.dart new file mode 100644 index 0000000..fedd0c5 --- /dev/null +++ b/lib/util/json_parsers.dart @@ -0,0 +1,42 @@ +import 'package:jmap_dart_client/http/converter/account_id_converter.dart'; +import 'package:jmap_dart_client/http/converter/calendar/event_id_nullable_converter.dart'; +import 'package:jmap_dart_client/http/converter/id_converter.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_id.dart'; + +class JsonParsers { + const JsonParsers._(); + factory JsonParsers() => _instance; + static const JsonParsers _instance = JsonParsers._(); + + AccountId parsingAccountId(Map json) { + return const AccountIdConverter().fromJson(json['accountId'] as String); + } + + List? parsingListId(Map json, String key) { + final jsonListIds = json[key]; + + if (jsonListIds == null) return null; + + if (jsonListIds.runtimeType != List) return null; + + return (jsonListIds as List) + .map((value) => const IdConverter().fromJson(value)) + .toList(); + } + + List? parsingListEventId(Map json, String key) { + final jsonListIds = json[key]; + + if (jsonListIds == null) return null; + + if (jsonListIds.runtimeType != List) return null; + + return (jsonListIds as List) + .map((value) => const EventIdNullableConverter().fromJson(value)) + .where((element) => element != null) + .cast() + .toList(); + } +} \ No newline at end of file diff --git a/test/jmap/mail/calendar/reply/calendar_event_accept_method_test.dart b/test/jmap/mail/calendar/reply/calendar_event_accept_method_test.dart new file mode 100644 index 0000000..6c94b61 --- /dev/null +++ b/test/jmap/mail/calendar/reply/calendar_event_accept_method_test.dart @@ -0,0 +1,89 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.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/id.dart'; +import 'package:jmap_dart_client/jmap/core/method/request/calendar_event_reply_method.dart'; +import 'package:jmap_dart_client/jmap/core/method/response/calendar_event_reply_response.dart'; +import 'package:jmap_dart_client/jmap/core/request/request_invocation.dart'; +import 'package:jmap_dart_client/jmap/jmap_request.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_id.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_method.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_response.dart'; + +void main() { + final baseOption = BaseOptions(method: 'POST'); + final dio = Dio(baseOption)..options.baseUrl = 'http://domain.com/jmap'; + final dioAdapter = DioAdapter(dio: dio); + final dioAdapterHeaders = {"accept": "application/json;jmapVersion=rfc-8621"}; + final httpClient = HttpClient(dio); + final processingInvocation = ProcessingInvocation(); + final requestBuilder = JmapRequestBuilder(httpClient, processingInvocation); + final accountId = AccountId(Id('123abc')); + final successBlobId = Id('abc123'); + final failureBlobId = Id('def456'); + final notFoundBlobId = Id('ghi789'); + final blobIds = [successBlobId, failureBlobId, notFoundBlobId]; + final methodCallId = MethodCallId('c0'); + + Map constructData(CalendarEventReplyMethod method) => { + "using": method.requiredCapabilities + .map((capability) => capability.value.toString()) + .toList(), + "methodCalls": [ + [ + method.methodName.value, + { + "accountId": accountId.id.value, + "blobIds": blobIds.map((id) => id.value).toList(), + }, + methodCallId.value + ] + ] + }; + + Map constructResponse(CalendarEventReplyMethod method) => { + "sessionState": "abcdefghij", + "methodResponses": [[ + method.methodName.value, + { + "accountId": accountId.id.value, + "accepted": [successBlobId.value], + "notCreated": [failureBlobId.value], + "notFound": [notFoundBlobId.value], + }, + methodCallId.value + ]] + }; + + group('calendar event accept method', () { + final method = CalendarEventAcceptMethod(accountId, blobIds: blobIds); + + test('should succeed with success blob data, ' + 'and fail with failure blob data ' + 'and not found with not found blob data', () async { + // arrange + final invocation = requestBuilder.invocation(method, methodCallId: methodCallId); + dioAdapter.onPost( + '', + (server) => server.reply(200, constructResponse(method)), + data: constructData(method), + headers: dioAdapterHeaders, + ); + + // act + final response = (await (requestBuilder..usings(method.requiredCapabilities)) + .build() + .execute()) + .parse( + invocation.methodCallId, + CalendarEventAcceptResponse.deserialize); + + // assert + expect((response as CalendarEventAcceptResponse?)?.accepted, equals([EventId(successBlobId.value)])); + expect(response?.notCreated, equals([failureBlobId])); + expect(response?.notFound, equals([notFoundBlobId])); + }); + }); +} \ No newline at end of file diff --git a/test/util/json_parsers_test.dart b/test/util/json_parsers_test.dart new file mode 100644 index 0000000..1d61fcf --- /dev/null +++ b/test/util/json_parsers_test.dart @@ -0,0 +1,118 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_id.dart'; +import 'package:jmap_dart_client/util/json_parsers.dart'; + +void main() { + final jsonParsers = JsonParsers(); + + group('json parsers test:', () { + group('parse account id test:', () { + test('should return account id when json contains account id',() { + // arrange + const rawAccountId = 'abc123'; + final json = {'accountId': rawAccountId}; + + // act + final accountId = jsonParsers.parsingAccountId(json); + + // assert + expect(accountId, AccountId(Id(rawAccountId))); + }); + + test('should throw TypeError exception when json doesn\'t contains account id',() { + // arrange + final json = {}; + + // assert + expect( + () => jsonParsers.parsingAccountId(json), + throwsA(isA())); + }); + + test('should throw TypeError exception when accountId json is not String',() { + // arrange + const rawAccountId = 'abc123'; + final json = {'accountId': [rawAccountId]}; + + // assert + expect( + () => jsonParsers.parsingAccountId(json), + throwsA(isA())); + }); + }); + + group('parse list id test:', () { + test('should return list id when json contains list id',() { + // arrange + const rawListId = ['abc123']; + final json = {'listId': rawListId}; + + // act + final listId = jsonParsers.parsingListId(json, 'listId'); + + // assert + expect(listId, rawListId.map((id) => Id(id)).toList()); + }); + + test('should return null when json doesn\'t contains list id',() { + // arrange + final json = {}; + + // act + final listId = jsonParsers.parsingListId(json, 'listId'); + + // assert + expect(listId, null); + }); + + test('should return null when list id json is not List',() { + // arrange + final json = {'listId': 'abc123'}; + + // act + final listId = jsonParsers.parsingListId(json, 'listId'); + + // assert + expect(listId, null); + }); + }); + + group('parse list event id test:', () { + test('should return list event id when json contains list event id',() { + // arrange + const rawListEventId = ['abc123']; + final json = {'listEventId': rawListEventId}; + + // act + final listEventId = jsonParsers.parsingListEventId(json, 'listEventId'); + + // assert + expect(listEventId, rawListEventId.map((id) => EventId(id)).toList()); + }); + + test('should return null when json doesn\'t contains list event id',() { + // arrange + final json = {}; + + // act + final listEventId = jsonParsers.parsingListEventId(json, 'listEventId'); + + // assert + expect(listEventId, null); + }); + + test('should return null when list event id json is not List',() { + // arrange + final json = {'listEventId': 'abc123'}; + + // act + final listEventId = jsonParsers.parsingListEventId(json, 'listEventId'); + + // assert + expect(listEventId, null); + }); + }); + }); +} \ No newline at end of file