Skip to content

Commit

Permalink
api: Add route updateUserTopic and its compat helper
Browse files Browse the repository at this point in the history
For the legacy case, there can be an error when muting a topic that
is already muted or unmuting one that is already unmuted.

The callers are not expected to handle such errors because they aren't
really actionable.

Similar to getMessageCompat, updateUserTopicCompat is expected to be
dropped, eventually.

Signed-off-by: Zixuan James Li <zixuan@zulip.com>
  • Loading branch information
PIG208 committed Dec 10, 2024
1 parent 92f47a5 commit 8c05e1c
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 0 deletions.
48 changes: 48 additions & 0 deletions lib/api/route/channels.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:json_annotation/json_annotation.dart';

import '../core.dart';
import '../model/model.dart';
part 'channels.g.dart';

/// https://zulip.com/api/get-stream-topics
Expand Down Expand Up @@ -38,3 +39,50 @@ class GetStreamTopicsEntry {

Map<String, dynamic> toJson() => _$GetStreamTopicsEntryToJson(this);
}

/// Update a topic's visibility policy.
///
/// This encapsulates a server-feature check.
// TODO(server-7): remove this and just use updateUserTopic
Future<void> updateUserTopicCompat(ApiConnection connection, {
required int streamId,
required String topic,
required UserTopicVisibilityPolicy visibilityPolicy,
}) {
final useLegacyApi = connection.zulipFeatureLevel! < 170;
if (useLegacyApi) {
assert(visibilityPolicy == UserTopicVisibilityPolicy.none
|| visibilityPolicy == UserTopicVisibilityPolicy.muted);
final op = visibilityPolicy == UserTopicVisibilityPolicy.none ? 'remove'
: 'add';
// https://zulip.com/api/mute-topic
return connection.patch('muteTopic', (_) {}, 'users/me/subscriptions/muted_topics', {
'stream_id': streamId,
'topic': RawParameter(topic),
'op': RawParameter(op),
});
} else {
return updateUserTopic(connection,
streamId: streamId,
topic: topic,
visibilityPolicy: visibilityPolicy);
}
}

/// https://zulip.com/api/update-user-topic
///
/// This binding only supports feature levels 170+.
// TODO(server-7) remove FL 170+ mention in doc, and the related `assert`
Future<void> updateUserTopic(ApiConnection connection, {
required int streamId,
required String topic,
required UserTopicVisibilityPolicy visibilityPolicy,
}) {
assert(visibilityPolicy != UserTopicVisibilityPolicy.unknown);
assert(connection.zulipFeatureLevel! >= 170);
return connection.post('updateUserTopic', (_) {}, 'user_topics', {
'stream_id': streamId,
'topic': RawParameter(topic),
'visibility_policy': visibilityPolicy,
});
}
98 changes: 98 additions & 0 deletions test/api/route/channels_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'package:checks/checks.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_test/flutter_test.dart';
import 'package:zulip/api/model/model.dart';
import 'package:zulip/api/route/channels.dart';

import '../../stdlib_checks.dart';
import '../fake_api.dart';

void main() {
test('smoke updateUserTopic', () {
return FakeApiConnection.with_((connection) async {
connection.prepare(json: {});
await updateUserTopic(connection,
streamId: 1, topic: 'topic',
visibilityPolicy: UserTopicVisibilityPolicy.followed);
check(connection.takeRequests()).single.isA<http.Request>()
..method.equals('POST')
..url.path.equals('/api/v1/user_topics')
..bodyFields.deepEquals({
'stream_id': '1',
'topic': 'topic',
'visibility_policy': '3',
});
});
});

test('updateUserTopic only accepts valid visibility policy', () {
return FakeApiConnection.with_((connection) async {
check(() => updateUserTopic(connection,
streamId: 1, topic: 'topic',
visibilityPolicy: UserTopicVisibilityPolicy.unknown),
).throws<AssertionError>();
});
});

test('updateUserTopicCompat when FL >= 170', () {
return FakeApiConnection.with_((connection) async {
connection.prepare(json: {});
await updateUserTopicCompat(connection,
streamId: 1, topic: 'topic',
visibilityPolicy: UserTopicVisibilityPolicy.followed);
check(connection.takeRequests()).single.isA<http.Request>()
..method.equals('POST')
..url.path.equals('/api/v1/user_topics')
..bodyFields.deepEquals({
'stream_id': '1',
'topic': 'topic',
'visibility_policy': '3',
});
});
});

group('legacy: use muteTopic when FL < 170', () {
test('updateUserTopic throws AssertionError when FL < 170', () {
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
check(() => updateUserTopic(connection,
streamId: 1, topic: 'topic',
visibilityPolicy: UserTopicVisibilityPolicy.followed),
).throws<AssertionError>();
});
});

test('policy: none -> op: remove', () {
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
connection.prepare(json: {});
await updateUserTopicCompat(connection,
streamId: 1, topic: 'topic',
visibilityPolicy: UserTopicVisibilityPolicy.none);
check(connection.takeRequests()).single.isA<http.Request>()
..method.equals('PATCH')
..url.path.equals('/api/v1/users/me/subscriptions/muted_topics')
..bodyFields.deepEquals({
'stream_id': '1',
'topic': 'topic',
'op': 'remove',
});
});
});

test('policy: muted -> op: add', () {
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
connection.prepare(json: {});
await updateUserTopicCompat(connection,
streamId: 1, topic: 'topic',
visibilityPolicy: UserTopicVisibilityPolicy.muted);
check(connection.takeRequests()).single.isA<http.Request>()
..method.equals('PATCH')
..url.path.equals('/api/v1/users/me/subscriptions/muted_topics')
..bodyFields.deepEquals({
'stream_id': '1',
'topic': 'topic',
'op': 'add',
});
});
});
});
}

0 comments on commit 8c05e1c

Please sign in to comment.