From 4ba572fd7c2fb18bdc77626da36b81c4cea34210 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 10 Dec 2024 15:40:52 -0800 Subject: [PATCH] api: Add updateMessage route And move the PropagateMode definition from events.dart to model.dart, since it's no longer specific to an event. --- lib/api/model/events.dart | 8 ---- lib/api/model/events.g.dart | 2 +- lib/api/model/model.dart | 10 +++++ lib/api/model/model.g.dart | 6 +++ lib/api/route/messages.dart | 33 +++++++++++++++++ lib/api/route/messages.g.dart | 7 ++++ test/api/route/messages_test.dart | 61 +++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 9 deletions(-) diff --git a/lib/api/model/events.dart b/lib/api/model/events.dart index f543144ce1..9faa3d367e 100644 --- a/lib/api/model/events.dart +++ b/lib/api/model/events.dart @@ -766,14 +766,6 @@ class UpdateMessageEvent extends Event { Map toJson() => _$UpdateMessageEventToJson(this); } -/// As in [UpdateMessageEvent.propagateMode]. -@JsonEnum(fieldRename: FieldRename.snake) -enum PropagateMode { - changeOne, - changeLater, - changeAll; -} - /// A Zulip event of type `delete_message`: https://zulip.com/api/get-events#delete_message @JsonSerializable(fieldRename: FieldRename.snake) class DeleteMessageEvent extends Event { diff --git a/lib/api/model/events.g.dart b/lib/api/model/events.g.dart index 9229bf9757..5d47444ecd 100644 --- a/lib/api/model/events.g.dart +++ b/lib/api/model/events.g.dart @@ -455,7 +455,7 @@ Map _$UpdateMessageEventToJson(UpdateMessageEvent instance) => 'edit_timestamp': instance.editTimestamp, 'stream_id': instance.origStreamId, 'new_stream_id': instance.newStreamId, - 'propagate_mode': _$PropagateModeEnumMap[instance.propagateMode], + 'propagate_mode': instance.propagateMode, 'orig_subject': instance.origTopic, 'subject': instance.newTopic, 'orig_content': instance.origContent, diff --git a/lib/api/model/model.dart b/lib/api/model/model.dart index 8aad0f05b4..03af104baf 100644 --- a/lib/api/model/model.dart +++ b/lib/api/model/model.dart @@ -939,3 +939,13 @@ enum MessageEditState { return MessageEditState.none; } } + +/// As in [updateMessage] or [UpdateMessageEvent.propagateMode]. +@JsonEnum(fieldRename: FieldRename.snake, alwaysCreate: true) +enum PropagateMode { + changeOne, + changeLater, + changeAll; + + String toJson() => _$PropagateModeEnumMap[this]!; +} diff --git a/lib/api/model/model.g.dart b/lib/api/model/model.g.dart index c8434a0fa8..cfa38aec5f 100644 --- a/lib/api/model/model.g.dart +++ b/lib/api/model/model.g.dart @@ -403,3 +403,9 @@ const _$MessageFlagEnumMap = { MessageFlag.historical: 'historical', MessageFlag.unknown: 'unknown', }; + +const _$PropagateModeEnumMap = { + PropagateMode.changeOne: 'change_one', + PropagateMode.changeLater: 'change_later', + PropagateMode.changeAll: 'change_all', +}; diff --git a/lib/api/route/messages.dart b/lib/api/route/messages.dart index ea7421733e..3374741c51 100644 --- a/lib/api/route/messages.dart +++ b/lib/api/route/messages.dart @@ -258,6 +258,39 @@ class SendMessageResult { Map toJson() => _$SendMessageResultToJson(this); } +/// https://zulip.com/api/update-message +Future updateMessage( + ApiConnection connection, { + required int messageId, + TopicName? topic, + PropagateMode? propagateMode, + bool? sendNotificationToOldThread, + bool? sendNotificationToNewThread, + String? content, + int? streamId, +}) { + return connection.patch('updateMessage', UpdateMessageResult.fromJson, 'messages/$messageId', { + if (topic != null) 'topic': RawParameter(topic.apiName), + if (propagateMode != null) 'propagate_mode': RawParameter(propagateMode.toJson()), + if (sendNotificationToOldThread != null) 'send_notification_to_old_thread': sendNotificationToOldThread, + if (sendNotificationToNewThread != null) 'send_notification_to_new_thread': sendNotificationToNewThread, + if (content != null) 'content': RawParameter(content), + if (streamId != null) 'stream_id': streamId, + }); +} + +@JsonSerializable(fieldRename: FieldRename.snake) +class UpdateMessageResult { + // final List detachedUploads; // TODO handle + + UpdateMessageResult(); + + factory UpdateMessageResult.fromJson(Map json) => + _$UpdateMessageResultFromJson(json); + + Map toJson() => _$UpdateMessageResultToJson(this); +} + /// https://zulip.com/api/upload-file Future uploadFile( ApiConnection connection, { diff --git a/lib/api/route/messages.g.dart b/lib/api/route/messages.g.dart index 161ea7588e..3a449d73ac 100644 --- a/lib/api/route/messages.g.dart +++ b/lib/api/route/messages.g.dart @@ -50,6 +50,13 @@ Map _$SendMessageResultToJson(SendMessageResult instance) => 'id': instance.id, }; +UpdateMessageResult _$UpdateMessageResultFromJson(Map json) => + UpdateMessageResult(); + +Map _$UpdateMessageResultToJson( + UpdateMessageResult instance) => + {}; + UploadFileResult _$UploadFileResultFromJson(Map json) => UploadFileResult( uri: json['uri'] as String, diff --git a/test/api/route/messages_test.dart b/test/api/route/messages_test.dart index eb8fbdac29..4da4334bae 100644 --- a/test/api/route/messages_test.dart +++ b/test/api/route/messages_test.dart @@ -420,6 +420,67 @@ void main() { }); }); + group('updateMessage', () { + Future checkUpdateMessage( + FakeApiConnection connection, { + required int messageId, + TopicName? topic, + PropagateMode? propagateMode, + bool? sendNotificationToOldThread, + bool? sendNotificationToNewThread, + String? content, + int? streamId, + required Map expected, + }) async { + final result = await updateMessage(connection, + messageId: messageId, + topic: topic, + propagateMode: propagateMode, + sendNotificationToOldThread: sendNotificationToOldThread, + sendNotificationToNewThread: sendNotificationToNewThread, + content: content, + streamId: streamId, + ); + check(connection.lastRequest).isA() + ..method.equals('PATCH') + ..url.path.equals('/api/v1/messages/$messageId') + ..bodyFields.deepEquals(expected); + return result; + } + + test('topic/content change', () { + // A separate test exercises `streamId`; + // the API doesn't allow changing channel and content at the same time. + return FakeApiConnection.with_((connection) async { + connection.prepare(json: UpdateMessageResult().toJson()); + await checkUpdateMessage(connection, + messageId: eg.streamMessage().id, + topic: eg.t('new topic'), + propagateMode: PropagateMode.changeAll, + sendNotificationToOldThread: true, + sendNotificationToNewThread: true, + content: 'asdf', + expected: { + 'topic': 'new topic', + 'propagate_mode': 'change_all', + 'send_notification_to_old_thread': 'true', + 'send_notification_to_new_thread': 'true', + 'content': 'asdf', + }); + }); + }); + + test('channel change', () { + return FakeApiConnection.with_((connection) async { + connection.prepare(json: UpdateMessageResult().toJson()); + await checkUpdateMessage(connection, + messageId: eg.streamMessage().id, + streamId: 1, + expected: {'stream_id': '1'}); + }); + }); + }); + group('uploadFile', () { Future checkUploadFile(FakeApiConnection connection, { required List> content,