diff --git a/lib/api/core.dart b/lib/api/core.dart index 39101f1420..b4d05b5b34 100644 --- a/lib/api/core.dart +++ b/lib/api/core.dart @@ -14,7 +14,7 @@ import 'exception.dart'; /// /// When updating this, also update [kMinSupportedZulipFeatureLevel] /// and the README. -// TODO(#268) address all TODO(server-5), TODO(server-6), and TODO(server-7) +// TODO(#992) address all TODO(server-6) and TODO(server-7) const kMinSupportedZulipVersion = '7.0'; /// The Zulip feature level reserved for the [kMinSupportedZulipVersion] release. diff --git a/lib/api/model/events.dart b/lib/api/model/events.dart index 6070616387..e472f151b7 100644 --- a/lib/api/model/events.dart +++ b/lib/api/model/events.dart @@ -383,7 +383,6 @@ class RealmUserUpdateEvent extends RealmUserEvent { @JsonKey(readValue: _readFromPerson) final String? timezone; @JsonKey(readValue: _readFromPerson) final int? botOwnerId; @JsonKey(readValue: _readFromPerson, unknownEnumValue: UserRole.unknown) final UserRole? role; - @JsonKey(readValue: _readFromPerson) final bool? isBillingAdmin; @JsonKey(readValue: _readNullableStringFromPerson) @NullableStringJsonConverter() @@ -421,7 +420,6 @@ class RealmUserUpdateEvent extends RealmUserEvent { this.timezone, this.botOwnerId, this.role, - this.isBillingAdmin, this.deliveryEmail, this.customProfileField, this.newEmail, @@ -928,13 +926,13 @@ class UpdateMessageEvent extends Event { @JsonKey(includeToJson: true) String get type => 'update_message'; - final int? userId; // TODO(server-5) - final bool? renderingOnly; // TODO(server-5) + final int? userId; + final bool renderingOnly; final int messageId; final List messageIds; final List flags; - final int? editTimestamp; // TODO(server-5) + final int editTimestamp; // final String? streamName; // ignore diff --git a/lib/api/model/events.g.dart b/lib/api/model/events.g.dart index 1aa93ef47b..bb85039555 100644 --- a/lib/api/model/events.g.dart +++ b/lib/api/model/events.g.dart @@ -201,8 +201,6 @@ RealmUserUpdateEvent _$RealmUserUpdateEventFromJson( RealmUserUpdateEvent._readFromPerson(json, 'role'), unknownValue: UserRole.unknown, ), - isBillingAdmin: - RealmUserUpdateEvent._readFromPerson(json, 'is_billing_admin') as bool?, deliveryEmail: _$JsonConverterFromJson, JsonNullable>( RealmUserUpdateEvent._readNullableStringFromPerson( @@ -235,7 +233,6 @@ Map _$RealmUserUpdateEventToJson( 'timezone': instance.timezone, 'bot_owner_id': instance.botOwnerId, 'role': instance.role, - 'is_billing_admin': instance.isBillingAdmin, 'delivery_email': _$JsonConverterToJson, JsonNullable>( instance.deliveryEmail, @@ -575,7 +572,7 @@ UpdateMessageEvent _$UpdateMessageEventFromJson(Map json) => UpdateMessageEvent( id: (json['id'] as num).toInt(), userId: (json['user_id'] as num?)?.toInt(), - renderingOnly: json['rendering_only'] as bool?, + renderingOnly: json['rendering_only'] as bool, messageId: (json['message_id'] as num).toInt(), messageIds: (json['message_ids'] as List) .map((e) => (e as num).toInt()) @@ -583,7 +580,7 @@ UpdateMessageEvent _$UpdateMessageEventFromJson(Map json) => flags: (json['flags'] as List) .map((e) => $enumDecode(_$MessageFlagEnumMap, e)) .toList(), - editTimestamp: (json['edit_timestamp'] as num?)?.toInt(), + editTimestamp: (json['edit_timestamp'] as num).toInt(), moveData: UpdateMessageMoveData.tryParseFromJson( UpdateMessageEvent._readMoveData(json, 'move_data') as Map, diff --git a/lib/api/model/initial_snapshot.dart b/lib/api/model/initial_snapshot.dart index 0f22f0b5f0..50a9f0c243 100644 --- a/lib/api/model/initial_snapshot.dart +++ b/lib/api/model/initial_snapshot.dart @@ -18,7 +18,7 @@ class InitialSnapshot { final int lastEventId; final int zulipFeatureLevel; final String zulipVersion; - final String? zulipMergeBase; // TODO(server-5) + final String zulipMergeBase; final List alertWords; @@ -340,15 +340,9 @@ class UnreadMessagesSnapshot { /// An item in [UnreadMessagesSnapshot.dms]. @JsonSerializable(fieldRename: FieldRename.snake) class UnreadDmSnapshot { - @JsonKey(readValue: _readOtherUserId) final int otherUserId; final List unreadMessageIds; - // TODO(server-5): Simplify away. - static dynamic _readOtherUserId(Map json, String key) { - return json[key] ?? json['sender_id']; - } - UnreadDmSnapshot({ required this.otherUserId, required this.unreadMessageIds, diff --git a/lib/api/model/initial_snapshot.g.dart b/lib/api/model/initial_snapshot.g.dart index e7a5923c89..d6de29713e 100644 --- a/lib/api/model/initial_snapshot.g.dart +++ b/lib/api/model/initial_snapshot.g.dart @@ -15,7 +15,7 @@ InitialSnapshot _$InitialSnapshotFromJson( lastEventId: (json['last_event_id'] as num).toInt(), zulipFeatureLevel: (json['zulip_feature_level'] as num).toInt(), zulipVersion: json['zulip_version'] as String, - zulipMergeBase: json['zulip_merge_base'] as String?, + zulipMergeBase: json['zulip_merge_base'] as String, alertWords: (json['alert_words'] as List) .map((e) => e as String) .toList(), @@ -310,9 +310,7 @@ Map _$UnreadMessagesSnapshotToJson( UnreadDmSnapshot _$UnreadDmSnapshotFromJson(Map json) => UnreadDmSnapshot( - otherUserId: - (UnreadDmSnapshot._readOtherUserId(json, 'other_user_id') as num) - .toInt(), + otherUserId: (json['other_user_id'] as num).toInt(), unreadMessageIds: (json['unread_message_ids'] as List) .map((e) => (e as num).toInt()) .toList(), diff --git a/lib/api/model/model.dart b/lib/api/model/model.dart index fca63412c2..6f0e502a39 100644 --- a/lib/api/model/model.dart +++ b/lib/api/model/model.dart @@ -414,7 +414,6 @@ class User { // bool isOwner; // obsoleted by [role]; ignore // bool isAdmin; // obsoleted by [role]; ignore // bool isGuest; // obsoleted by [role]; ignore - bool? isBillingAdmin; // TODO(server-5) final bool isBot; final int? botType; // TODO enum int? botOwnerId; @@ -430,7 +429,9 @@ class User { @JsonKey(readValue: _readProfileData) Map? profileData; - @JsonKey(readValue: _readIsSystemBot) + // This field is absent in `realm_users` and `realm_non_active_users`, + // which contain no system bots; it's present in `cross_realm_bots`. + @JsonKey(defaultValue: false) final bool isSystemBot; static Map? _readProfileData(Map json, String key) { @@ -442,14 +443,6 @@ class User { return (value != null && value.isNotEmpty) ? value : null; } - static bool _readIsSystemBot(Map json, String key) { - // This field is absent in `realm_users` and `realm_non_active_users`, - // which contain no system bots; it's present in `cross_realm_bots`. - return (json[key] as bool?) - ?? (json['is_cross_realm_bot'] as bool?) // TODO(server-5): renamed to `is_system_bot` - ?? false; - } - User({ required this.userId, required this.deliveryEmail, @@ -457,7 +450,6 @@ class User { required this.fullName, required this.dateJoined, required this.isActive, - required this.isBillingAdmin, required this.isBot, required this.botType, required this.botOwnerId, @@ -1271,18 +1263,11 @@ enum MessageEditState { continue; } - // TODO(server-5) prev_subject was the old name of prev_topic on pre-5.0 servers - final prevTopicStr = (entry['prev_topic'] ?? entry['prev_subject']) as String?; - final prevTopic = prevTopicStr == null ? null : TopicName.fromJson(prevTopicStr); - final topicStr = entry['topic'] as String?; - final topic = topicStr == null ? null : TopicName.fromJson(topicStr); - if (prevTopic != null) { - // TODO(server-5) pre-5.0 servers do not have the 'topic' field - if (topic == null) { - hasMoved = true; - } else { - hasMoved |= !topicMoveWasResolveOrUnresolve(topic, prevTopic); - } + final prevTopicStr = entry['prev_topic'] as String?; + if (prevTopicStr != null) { + final prevTopic = TopicName.fromJson(prevTopicStr); + final topic = TopicName.fromJson(entry['topic'] as String); + hasMoved |= !topicMoveWasResolveOrUnresolve(topic, prevTopic); } } diff --git a/lib/api/model/model.g.dart b/lib/api/model/model.g.dart index a4608a08c8..e9cf30e5d2 100644 --- a/lib/api/model/model.g.dart +++ b/lib/api/model/model.g.dart @@ -117,7 +117,6 @@ User _$UserFromJson(Map json) => User( fullName: json['full_name'] as String, dateJoined: json['date_joined'] as String, isActive: json['is_active'] as bool, - isBillingAdmin: json['is_billing_admin'] as bool?, isBot: json['is_bot'] as bool, botType: (json['bot_type'] as num?)?.toInt(), botOwnerId: (json['bot_owner_id'] as num?)?.toInt(), @@ -137,7 +136,7 @@ User _$UserFromJson(Map json) => User( ProfileFieldUserData.fromJson(e as Map), ), ), - isSystemBot: User._readIsSystemBot(json, 'is_system_bot') as bool, + isSystemBot: json['is_system_bot'] as bool? ?? false, ); Map _$UserToJson(User instance) => { @@ -147,7 +146,6 @@ Map _$UserToJson(User instance) => { 'full_name': instance.fullName, 'date_joined': instance.dateJoined, 'is_active': instance.isActive, - 'is_billing_admin': instance.isBillingAdmin, 'is_bot': instance.isBot, 'bot_type': instance.botType, 'bot_owner_id': instance.botOwnerId, diff --git a/lib/api/model/web_auth.dart b/lib/api/model/web_auth.dart index 490c4b79db..9de8992988 100644 --- a/lib/api/model/web_auth.dart +++ b/lib/api/model/web_auth.dart @@ -7,7 +7,7 @@ import 'package:flutter/foundation.dart'; class WebAuthPayload { final Uri realm; final String email; - final int? userId; // TODO(server-5) new in FL 108 + final int userId; final String otpEncryptedApiKey; WebAuthPayload._({ @@ -25,7 +25,7 @@ class WebAuthPayload { queryParameters: { 'realm': String realmStr, 'email': String email, - // 'user_id' handled below + 'user_id': String userIdStr, 'otp_encrypted_api_key': String otpEncryptedApiKey, }, ) @@ -33,13 +33,8 @@ class WebAuthPayload { final Uri? realm = Uri.tryParse(realmStr); if (realm == null) throw const FormatException(); - // TODO(server-5) require in queryParameters (new in FL 108) - final userIdStr = url.queryParameters['user_id']; - int? userId; - if (userIdStr != null) { - userId = int.tryParse(userIdStr, radix: 10); - if (userId == null) throw const FormatException(); - } + final int? userId = int.tryParse(userIdStr, radix: 10); + if (userId == null) throw const FormatException(); if (!RegExp(r'^[0-9a-fA-F]{64}$').hasMatch(otpEncryptedApiKey)) { throw const FormatException(); diff --git a/lib/api/notifications.dart b/lib/api/notifications.dart index 6d028aa267..9c5df3c749 100644 --- a/lib/api/notifications.dart +++ b/lib/api/notifications.dart @@ -177,13 +177,9 @@ sealed class FcmMessageRecipient { @JsonSerializable(fieldRename: FieldRename.snake, createToJson: false) @_IntConverter() class FcmMessageChannelRecipient extends FcmMessageRecipient { - // Sending the stream ID in notifications is new in Zulip Server 5. - // But handling the lack of it would add complication, and we don't strictly - // need to -- we intend (#268) to cut pre-server-5 support before beta release. - // TODO(server-5): cut comment final int streamId; - // Current servers (as of 2023) always send the stream name. But + // Current servers (as of 2025) always send the stream name. But // future servers might not, once clients get the name from local data. // So might as well be ready. @JsonKey(name: 'stream') diff --git a/lib/api/route/messages.dart b/lib/api/route/messages.dart index 428a7c9a95..7460a02461 100644 --- a/lib/api/route/messages.dart +++ b/lib/api/route/messages.dart @@ -1,69 +1,18 @@ import 'package:json_annotation/json_annotation.dart'; import '../core.dart'; -import '../exception.dart'; import '../model/model.dart'; import '../model/narrow.dart'; part 'messages.g.dart'; -/// Convenience function to get a single message from any server. -/// -/// This encapsulates a server-feature check. -/// -/// Gives null if the server reports that the message doesn't exist. -// TODO(server-5) Simplify this away; just use getMessage. -Future getMessageCompat(ApiConnection connection, { - required int messageId, - bool? applyMarkdown, - required bool allowEmptyTopicName, -}) async { - final useLegacyApi = connection.zulipFeatureLevel! < 120; - if (useLegacyApi) { - final response = await getMessages(connection, - narrow: [ApiNarrowMessageId(messageId)], - anchor: NumericAnchor(messageId), - numBefore: 0, - numAfter: 0, - applyMarkdown: applyMarkdown, - allowEmptyTopicName: allowEmptyTopicName, - - // Hard-code this param to `true`, as the new single-message API - // effectively does: - // https://chat.zulip.org/#narrow/stream/378-api-design/topic/.60client_gravatar.60.20in.20.60messages.2F.7Bmessage_id.7D.60/near/1418337 - clientGravatar: true, - ); - return response.messages.firstOrNull; - } else { - try { - final response = await getMessage(connection, - messageId: messageId, - applyMarkdown: applyMarkdown, - allowEmptyTopicName: allowEmptyTopicName, - ); - return response.message; - } on ZulipApiException catch (e) { - if (e.code == 'BAD_REQUEST') { - // Servers use this code when the message doesn't exist, according to - // the example in the doc. - return null; - } - rethrow; - } - } -} - /// https://zulip.com/api/get-message -/// -/// This binding only supports feature levels 120+. -// TODO(server-5) remove FL 120+ mention in doc, and the related `assert` Future getMessage(ApiConnection connection, { required int messageId, bool? applyMarkdown, required bool allowEmptyTopicName, }) { assert(allowEmptyTopicName, '`allowEmptyTopicName` should only be true'); - assert(connection.zulipFeatureLevel! >= 120); return connection.get('getMessage', GetMessageResult.fromJson, 'messages/$messageId', { if (applyMarkdown != null) 'apply_markdown': applyMarkdown, 'allow_empty_topic_name': allowEmptyTopicName, diff --git a/lib/api/route/realm.dart b/lib/api/route/realm.dart index a43c2e9921..282790bef2 100644 --- a/lib/api/route/realm.dart +++ b/lib/api/route/realm.dart @@ -36,7 +36,7 @@ class GetServerSettingsResult { final int zulipFeatureLevel; final String zulipVersion; - final String? zulipMergeBase; // TODO(server-5) + final String zulipMergeBase; final bool pushNotificationsEnabled; final bool isIncompatible; @@ -50,7 +50,7 @@ class GetServerSettingsResult { final String realmName; final String realmIcon; final String realmDescription; - final bool? realmWebPublicAccessEnabled; // TODO(server-5) + final bool realmWebPublicAccessEnabled; GetServerSettingsResult({ required this.authenticationMethods, diff --git a/lib/api/route/realm.g.dart b/lib/api/route/realm.g.dart index cb7e94b48c..c71df7d433 100644 --- a/lib/api/route/realm.g.dart +++ b/lib/api/route/realm.g.dart @@ -24,7 +24,7 @@ GetServerSettingsResult _$GetServerSettingsResultFromJson( .toList(), zulipFeatureLevel: (json['zulip_feature_level'] as num).toInt(), zulipVersion: json['zulip_version'] as String, - zulipMergeBase: json['zulip_merge_base'] as String?, + zulipMergeBase: json['zulip_merge_base'] as String, pushNotificationsEnabled: json['push_notifications_enabled'] as bool, isIncompatible: json['is_incompatible'] as bool, emailAuthEnabled: json['email_auth_enabled'] as bool, @@ -33,7 +33,7 @@ GetServerSettingsResult _$GetServerSettingsResultFromJson( realmName: json['realm_name'] as String, realmIcon: json['realm_icon'] as String, realmDescription: json['realm_description'] as String, - realmWebPublicAccessEnabled: json['realm_web_public_access_enabled'] as bool?, + realmWebPublicAccessEnabled: json['realm_web_public_access_enabled'] as bool, ); Map _$GetServerSettingsResultToJson( diff --git a/lib/model/emoji.dart b/lib/model/emoji.dart index c15ab8e2da..12424104bf 100644 --- a/lib/model/emoji.dart +++ b/lib/model/emoji.dart @@ -18,9 +18,9 @@ sealed class EmojiDisplay { EmojiDisplay({required this.emojiName}); - EmojiDisplay resolve(UserSettings? userSettings) { // TODO(server-5) + EmojiDisplay resolve(UserSettings userSettings) { if (this is TextEmojiDisplay) return this; - if (userSettings?.emojiset == Emojiset.text) { + if (userSettings.emojiset == Emojiset.text) { return TextEmojiDisplay(emojiName: emojiName); } return this; diff --git a/lib/model/message.dart b/lib/model/message.dart index 24788bdd1e..ac5e65186b 100644 --- a/lib/model/message.dart +++ b/lib/model/message.dart @@ -403,9 +403,7 @@ class MessageStoreImpl extends HasRealmStore with MessageStore, _OutboxMessageSt } void _handleUpdateMessageEventTimestamp(UpdateMessageEvent event) { - // TODO(server-5): Cut this fallback; rely on renderingOnly from FL 114 - final isRenderingOnly = event.renderingOnly ?? (event.userId == null); - if (event.editTimestamp == null || isRenderingOnly) { + if (event.renderingOnly) { // A rendering-only update gets omitted from the message edit history, // and [Message.lastEditTimestamp] is the last timestamp of that history. // So on a rendering-only update, the timestamp doesn't get updated. diff --git a/lib/model/server_support.dart b/lib/model/server_support.dart index c9922fffcc..f09a1c5adf 100644 --- a/lib/model/server_support.dart +++ b/lib/model/server_support.dart @@ -47,7 +47,13 @@ class ZulipVersionData { } final String zulipVersion; + + // The `zulip_merge_base` field was added in server-5, feature level 88. + // We leave it nullable on this class because if a user attempts to connect + // to an ancient Zulip server missing this field, we still want to capture + // the rest of the version data for use in the error message. final String? zulipMergeBase; + final int zulipFeatureLevel; bool matchesAccount(Account account) => diff --git a/lib/model/user.dart b/lib/model/user.dart index 522b204773..da6b828014 100644 --- a/lib/model/user.dart +++ b/lib/model/user.dart @@ -270,7 +270,6 @@ class UserStoreImpl extends HasRealmStore with UserStore { if (event.timezone != null) user.timezone = event.timezone!; if (event.botOwnerId != null) user.botOwnerId = event.botOwnerId!; if (event.role != null) user.role = event.role!; - if (event.isBillingAdmin != null) user.isBillingAdmin = event.isBillingAdmin!; if (event.deliveryEmail != null) user.deliveryEmail = event.deliveryEmail!.value; if (event.newEmail != null) user.email = event.newEmail!; if (event.isActive != null) user.isActive = event.isActive!; diff --git a/lib/notifications/open.dart b/lib/notifications/open.dart index 2eb281473c..1152148d48 100644 --- a/lib/notifications/open.dart +++ b/lib/notifications/open.dart @@ -239,9 +239,6 @@ class NotificationOpenPayload { final narrow = switch (zulipData) { { 'recipient_type': 'stream', - // TODO(server-5) remove this comment. - // We require 'stream_id' here but that is new from Server 5.0, - // resulting in failure on pre-5.0 servers. 'stream_id': final int streamId, 'topic': final String topic, } => diff --git a/lib/widgets/actions.dart b/lib/widgets/actions.dart index 4d96727666..4c725f9878 100644 --- a/lib/widgets/actions.dart +++ b/lib/widgets/actions.dart @@ -206,16 +206,17 @@ abstract final class ZulipAction { // On final failure or success, auto-dismiss the snackbar. final zulipLocalizations = ZulipLocalizations.of(context); try { - fetchedMessage = await getMessageCompat(PerAccountStoreWidget.of(context).connection, + fetchedMessage = (await getMessage(PerAccountStoreWidget.of(context).connection, messageId: messageId, applyMarkdown: false, allowEmptyTopicName: true, - ); - if (fetchedMessage == null) { - errorMessage = zulipLocalizations.errorMessageDoesNotSeemToExist; - } + )).message; } catch (e) { switch (e) { + case ZulipApiException(code: 'BAD_REQUEST'): + // Servers use this code when the message doesn't exist, according to + // the example in the doc: https://zulip.com/api/get-message + errorMessage = zulipLocalizations.errorMessageDoesNotSeemToExist; case ZulipApiException(): errorMessage = e.message; // TODO specific messages for common errors, like network errors diff --git a/lib/widgets/login.dart b/lib/widgets/login.dart index fa69a48c09..465c5409bf 100644 --- a/lib/widgets/login.dart +++ b/lib/widgets/login.dart @@ -329,8 +329,7 @@ class _LoginPageState extends State { if (payload.realm.origin != widget.serverSettings.realmUrl.origin) throw Error(); final apiKey = payload.decodeApiKey(_otp!); await _tryInsertAccountAndNavigate( - // TODO(server-5): Rely on userId from payload. - userId: payload.userId ?? await _getUserId(payload.email, apiKey), + userId: payload.userId, email: payload.email, apiKey: apiKey, ); diff --git a/test/api/model/events_checks.dart b/test/api/model/events_checks.dart index 59d01d67ac..d29a7acb65 100644 --- a/test/api/model/events_checks.dart +++ b/test/api/model/events_checks.dart @@ -23,7 +23,6 @@ extension RealmUserUpdateEventChecks on Subject { Subject get timezone => has((e) => e.timezone, 'timezone'); Subject get botOwnerId => has((e) => e.botOwnerId, 'botOwnerId'); Subject get role => has((e) => e.role, 'role'); - Subject get isBillingAdmin => has((e) => e.isBillingAdmin, 'isBillingAdmin'); Subject get customProfileField => has((e) => e.customProfileField, 'customProfileField'); Subject get newEmail => has((e) => e.newEmail, 'newEmail'); Subject?> get deliveryEmail => has((e) => e.deliveryEmail, 'deliveryEmail'); @@ -44,11 +43,11 @@ extension MessageEventChecks on Subject { extension UpdateMessageEventChecks on Subject { Subject get userId => has((e) => e.userId, 'userId'); - Subject get renderingOnly => has((e) => e.renderingOnly, 'renderingOnly'); + Subject get renderingOnly => has((e) => e.renderingOnly, 'renderingOnly'); Subject get messageId => has((e) => e.messageId, 'messageId'); Subject> get messageIds => has((e) => e.messageIds, 'messageIds'); Subject> get flags => has((e) => e.flags, 'flags'); - Subject get editTimestamp => has((e) => e.editTimestamp, 'editTimestamp'); + Subject get editTimestamp => has((e) => e.editTimestamp, 'editTimestamp'); Subject get moveData => has((e) => e.moveData, 'moveData'); Subject get origContent => has((e) => e.origContent, 'origContent'); Subject get origRenderedContent => has((e) => e.origRenderedContent, 'origRenderedContent'); diff --git a/test/api/model/model_checks.dart b/test/api/model/model_checks.dart index 201a0ef7b6..236d5b869f 100644 --- a/test/api/model/model_checks.dart +++ b/test/api/model/model_checks.dart @@ -34,7 +34,6 @@ extension UserChecks on Subject { Subject get fullName => has((x) => x.fullName, 'fullName'); Subject get dateJoined => has((x) => x.dateJoined, 'dateJoined'); Subject get isActive => has((x) => x.isActive, 'isActive'); - Subject get isBillingAdmin => has((x) => x.isBillingAdmin, 'isBillingAdmin'); Subject get isBot => has((x) => x.isBot, 'isBot'); Subject get botType => has((x) => x.botType, 'botType'); Subject get botOwnerId => has((x) => x.botOwnerId, 'botOwnerId'); diff --git a/test/api/model/model_test.dart b/test/api/model/model_test.dart index 8717ebbb6e..f04bbd6d4b 100644 --- a/test/api/model/model_test.dart +++ b/test/api/model/model_test.dart @@ -102,7 +102,6 @@ void main() { 'is_owner': false, 'is_admin': false, 'is_guest': false, - 'is_billing_admin': false, 'is_bot': false, 'role': 400, 'timezone': 'UTC', @@ -128,7 +127,6 @@ void main() { test('is_system_bot', () { check(mkUser({}).isSystemBot).isFalse(); - check(mkUser({'is_cross_realm_bot': true}).isSystemBot).isTrue(); check(mkUser({'is_system_bot': true}).isSystemBot).isTrue(); }); }); @@ -335,16 +333,6 @@ void main() { checkEditState(MessageEditState.edited, [{'prev_content': 'old_content'}]); }); - - test("'prev_topic' present without the 'topic' field -> moved", () { - checkEditState(MessageEditState.moved, - [{'prev_topic': 'old_topic'}]); - }); - - test("'prev_subject' present from a pre-5.0 server -> moved", () { - checkEditState(MessageEditState.moved, - [{'prev_subject': 'old_topic'}]); - }); }); group('topic resolved in edit history', () { diff --git a/test/api/model/web_auth_test.dart b/test/api/model/web_auth_test.dart index 01a670103f..7f8c326ed6 100644 --- a/test/api/model/web_auth_test.dart +++ b/test/api/model/web_auth_test.dart @@ -23,19 +23,6 @@ void main() { check(payload.decodeApiKey(otp)).equals(eg.selfAccount.apiKey); }); - // TODO(server-5) remove - test('legacy: no userId', () { - final queryParams = {...wellFormed.queryParameters}..remove('user_id'); - final payload = WebAuthPayload.parse( - wellFormed.replace(queryParameters: queryParams)); - check(payload) - ..otpEncryptedApiKey.equals(encryptedApiKey) - ..email.equals('self@example') - ..userId.isNull() - ..realm.equals(Uri.parse('https://chat.example/')); - check(payload.decodeApiKey(otp)).equals(eg.selfAccount.apiKey); - }); - test('parse fails when an expected field is missing', () { final queryParams = {...wellFormed.queryParameters}..remove('email'); final input = wellFormed.replace(queryParameters: queryParams); @@ -93,6 +80,6 @@ void main() { extension WebAuthPayloadChecks on Subject { Subject get otpEncryptedApiKey => has((x) => x.otpEncryptedApiKey, 'otpEncryptedApiKey'); Subject get email => has((x) => x.email, 'email'); - Subject get userId => has((x) => x.userId, 'userId'); + Subject get userId => has((x) => x.userId, 'userId'); Subject get realm => has((x) => x.realm, 'realm'); } diff --git a/test/api/route/messages_test.dart b/test/api/route/messages_test.dart index 24be32fba2..b001316a40 100644 --- a/test/api/route/messages_test.dart +++ b/test/api/route/messages_test.dart @@ -15,118 +15,6 @@ import '../fake_api.dart'; import 'route_checks.dart'; void main() { - group('getMessageCompat', () { - Future checkGetMessageCompat(FakeApiConnection connection, { - required bool expectLegacy, - required int messageId, - bool? applyMarkdown, - required bool allowEmptyTopicName, - }) async { - final result = await getMessageCompat(connection, - messageId: messageId, - applyMarkdown: applyMarkdown, - allowEmptyTopicName: allowEmptyTopicName, - ); - if (expectLegacy) { - check(connection.lastRequest).isA() - ..method.equals('GET') - ..url.path.equals('/api/v1/messages') - ..url.queryParameters.deepEquals({ - 'narrow': jsonEncode([ApiNarrowMessageId(messageId)]), - 'anchor': messageId.toString(), - 'num_before': '0', - 'num_after': '0', - if (applyMarkdown != null) 'apply_markdown': applyMarkdown.toString(), - 'allow_empty_topic_name': allowEmptyTopicName.toString(), - 'client_gravatar': 'true', - }); - } else { - check(connection.lastRequest).isA() - ..method.equals('GET') - ..url.path.equals('/api/v1/messages/$messageId') - ..url.queryParameters.deepEquals({ - if (applyMarkdown != null) 'apply_markdown': applyMarkdown.toString(), - 'allow_empty_topic_name': allowEmptyTopicName.toString(), - }); - } - return result; - } - - test('modern; message found', () { - return FakeApiConnection.with_((connection) async { - final message = eg.streamMessage(); - final fakeResult = GetMessageResult(message: message); - connection.prepare(json: fakeResult.toJson()); - final result = await checkGetMessageCompat(connection, - expectLegacy: false, - messageId: message.id, - applyMarkdown: true, - allowEmptyTopicName: true, - ); - check(result).isNotNull().jsonEquals(message); - }); - }); - - test('modern; message not found', () { - return FakeApiConnection.with_((connection) async { - final message = eg.streamMessage(); - connection.prepare( - apiException: eg.apiBadRequest(message: 'Invalid message(s)')); - final result = await checkGetMessageCompat(connection, - expectLegacy: false, - messageId: message.id, - applyMarkdown: true, - allowEmptyTopicName: true, - ); - check(result).isNull(); - }); - }); - - test('legacy; message found', () { - return FakeApiConnection.with_(zulipFeatureLevel: 119, (connection) async { - final message = eg.streamMessage(); - final fakeResult = GetMessagesResult( - anchor: message.id, - foundNewest: false, - foundOldest: false, - foundAnchor: true, - historyLimited: false, - messages: [message], - ); - connection.prepare(json: fakeResult.toJson()); - final result = await checkGetMessageCompat(connection, - expectLegacy: true, - messageId: message.id, - applyMarkdown: true, - allowEmptyTopicName: true, - ); - check(result).isNotNull().jsonEquals(message); - }); - }); - - test('legacy; message not found', () { - return FakeApiConnection.with_(zulipFeatureLevel: 119, (connection) async { - final message = eg.streamMessage(); - final fakeResult = GetMessagesResult( - anchor: message.id, - foundNewest: false, - foundOldest: false, - foundAnchor: false, - historyLimited: false, - messages: [], - ); - connection.prepare(json: fakeResult.toJson()); - final result = await checkGetMessageCompat(connection, - expectLegacy: true, - messageId: message.id, - applyMarkdown: true, - allowEmptyTopicName: true, - ); - check(result).isNull(); - }); - }); - }); - group('getMessage', () { Future checkGetMessage( FakeApiConnection connection, { @@ -186,16 +74,6 @@ void main() { expected: {'allow_empty_topic_name': 'true'}); }); }); - - test('Throws assertion error when FL <120', () { - return FakeApiConnection.with_(zulipFeatureLevel: 119, (connection) async { - connection.prepare(json: fakeResult.toJson()); - check(() => getMessage(connection, - messageId: 1, - allowEmptyTopicName: true, - )).throws(); - }); - }); }); test('ApiNarrow.toJson', () { diff --git a/test/example_data.dart b/test/example_data.dart index 8761465db9..9ad6e8ca13 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -274,7 +274,6 @@ User user({ fullName: fullName ?? 'A user', // TODO generate example names dateJoined: dateJoined ?? '2024-02-24T11:18+00:00', isActive: isActive ?? true, - isBillingAdmin: false, isBot: isBot ?? false, botType: null, botOwnerId: null, @@ -331,7 +330,6 @@ class _ImmutableUser extends User { fullName: user.fullName, dateJoined: user.dateJoined, isActive: user.isActive, - isBillingAdmin: user.isBillingAdmin, isBot: user.isBot, botType: user.botType, botOwnerId: user.botOwnerId, @@ -356,7 +354,6 @@ class _ImmutableUser extends User { @override set fullName(_) => throw _error; // dateJoined already immutable @override set isActive(_) => throw _error; - @override set isBillingAdmin(_) => throw _error; // isBot already immutable // botType already immutable @override set botOwnerId(_) => throw _error; @@ -943,7 +940,7 @@ DeleteMessageEvent deleteMessageEvent(List messages) { UpdateMessageEvent updateMessageEditEvent( Message origMessage, { int? userId = -1, // null means null; default is [selfUser.userId] - bool? renderingOnly = false, + bool renderingOnly = false, int? messageId, List? flags, int? editTimestamp, diff --git a/test/model/message_test.dart b/test/model/message_test.dart index 69f349c02d..a7d60e8fac 100644 --- a/test/model/message_test.dart +++ b/test/model/message_test.dart @@ -984,15 +984,14 @@ void main() { ..content.not((it) => it.equals(updateEvent.renderedContent!)); }); - // TODO(server-5): Cut legacy case for rendering-only message update - Future checkRenderingOnly({required bool legacy}) async { + test('rendering-only update does not change timestamp', () async { final originalMessage = eg.streamMessage( lastEditTimestamp: 78492, content: "

Hello, world

"); final updateEvent = eg.updateMessageEditEvent(originalMessage, renderedContent: "

Hello, world

Some link preview
", editTimestamp: 99999, - renderingOnly: legacy ? null : true, + renderingOnly: true, userId: null, ); await prepare(); @@ -1008,14 +1007,6 @@ void main() { // ... edit timestamp is not. ..lastEditTimestamp.equals(originalMessage.lastEditTimestamp) ..lastEditTimestamp.not((it) => it.equals(updateEvent.editTimestamp)); - } - - test('rendering-only update does not change timestamp', () async { - await checkRenderingOnly(legacy: false); - }); - - test('rendering-only update does not change timestamp (for old server versions)', () async { - await checkRenderingOnly(legacy: true); }); group('Handle message edit state update', () { diff --git a/test/model/store_checks.dart b/test/model/store_checks.dart index b56732ef4f..d9032bbc8a 100644 --- a/test/model/store_checks.dart +++ b/test/model/store_checks.dart @@ -58,7 +58,7 @@ extension PerAccountStoreChecks on Subject { Subject get accountId => has((x) => x.accountId, 'accountId'); Subject get account => has((x) => x.account, 'account'); Subject get selfUserId => has((x) => x.selfUserId, 'selfUserId'); - Subject get userSettings => has((x) => x.userSettings, 'userSettings'); + Subject get userSettings => has((x) => x.userSettings, 'userSettings'); Subject> get savedSnippets => has((x) => x.savedSnippets, 'savedSnippets'); Subject> get streams => has((x) => x.streams, 'streams'); Subject> get streamsByName => has((x) => x.streamsByName, 'streamsByName');