Skip to content

Commit 8c05e1c

Browse files
committed
api: Add route updateUserTopic and its compat helper
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 <[email protected]>
1 parent 92f47a5 commit 8c05e1c

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

lib/api/route/channels.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:json_annotation/json_annotation.dart';
22

33
import '../core.dart';
4+
import '../model/model.dart';
45
part 'channels.g.dart';
56

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

3940
Map<String, dynamic> toJson() => _$GetStreamTopicsEntryToJson(this);
4041
}
42+
43+
/// Update a topic's visibility policy.
44+
///
45+
/// This encapsulates a server-feature check.
46+
// TODO(server-7): remove this and just use updateUserTopic
47+
Future<void> updateUserTopicCompat(ApiConnection connection, {
48+
required int streamId,
49+
required String topic,
50+
required UserTopicVisibilityPolicy visibilityPolicy,
51+
}) {
52+
final useLegacyApi = connection.zulipFeatureLevel! < 170;
53+
if (useLegacyApi) {
54+
assert(visibilityPolicy == UserTopicVisibilityPolicy.none
55+
|| visibilityPolicy == UserTopicVisibilityPolicy.muted);
56+
final op = visibilityPolicy == UserTopicVisibilityPolicy.none ? 'remove'
57+
: 'add';
58+
// https://zulip.com/api/mute-topic
59+
return connection.patch('muteTopic', (_) {}, 'users/me/subscriptions/muted_topics', {
60+
'stream_id': streamId,
61+
'topic': RawParameter(topic),
62+
'op': RawParameter(op),
63+
});
64+
} else {
65+
return updateUserTopic(connection,
66+
streamId: streamId,
67+
topic: topic,
68+
visibilityPolicy: visibilityPolicy);
69+
}
70+
}
71+
72+
/// https://zulip.com/api/update-user-topic
73+
///
74+
/// This binding only supports feature levels 170+.
75+
// TODO(server-7) remove FL 170+ mention in doc, and the related `assert`
76+
Future<void> updateUserTopic(ApiConnection connection, {
77+
required int streamId,
78+
required String topic,
79+
required UserTopicVisibilityPolicy visibilityPolicy,
80+
}) {
81+
assert(visibilityPolicy != UserTopicVisibilityPolicy.unknown);
82+
assert(connection.zulipFeatureLevel! >= 170);
83+
return connection.post('updateUserTopic', (_) {}, 'user_topics', {
84+
'stream_id': streamId,
85+
'topic': RawParameter(topic),
86+
'visibility_policy': visibilityPolicy,
87+
});
88+
}

test/api/route/channels_test.dart

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import 'package:checks/checks.dart';
2+
import 'package:http/http.dart' as http;
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:zulip/api/model/model.dart';
5+
import 'package:zulip/api/route/channels.dart';
6+
7+
import '../../stdlib_checks.dart';
8+
import '../fake_api.dart';
9+
10+
void main() {
11+
test('smoke updateUserTopic', () {
12+
return FakeApiConnection.with_((connection) async {
13+
connection.prepare(json: {});
14+
await updateUserTopic(connection,
15+
streamId: 1, topic: 'topic',
16+
visibilityPolicy: UserTopicVisibilityPolicy.followed);
17+
check(connection.takeRequests()).single.isA<http.Request>()
18+
..method.equals('POST')
19+
..url.path.equals('/api/v1/user_topics')
20+
..bodyFields.deepEquals({
21+
'stream_id': '1',
22+
'topic': 'topic',
23+
'visibility_policy': '3',
24+
});
25+
});
26+
});
27+
28+
test('updateUserTopic only accepts valid visibility policy', () {
29+
return FakeApiConnection.with_((connection) async {
30+
check(() => updateUserTopic(connection,
31+
streamId: 1, topic: 'topic',
32+
visibilityPolicy: UserTopicVisibilityPolicy.unknown),
33+
).throws<AssertionError>();
34+
});
35+
});
36+
37+
test('updateUserTopicCompat when FL >= 170', () {
38+
return FakeApiConnection.with_((connection) async {
39+
connection.prepare(json: {});
40+
await updateUserTopicCompat(connection,
41+
streamId: 1, topic: 'topic',
42+
visibilityPolicy: UserTopicVisibilityPolicy.followed);
43+
check(connection.takeRequests()).single.isA<http.Request>()
44+
..method.equals('POST')
45+
..url.path.equals('/api/v1/user_topics')
46+
..bodyFields.deepEquals({
47+
'stream_id': '1',
48+
'topic': 'topic',
49+
'visibility_policy': '3',
50+
});
51+
});
52+
});
53+
54+
group('legacy: use muteTopic when FL < 170', () {
55+
test('updateUserTopic throws AssertionError when FL < 170', () {
56+
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
57+
check(() => updateUserTopic(connection,
58+
streamId: 1, topic: 'topic',
59+
visibilityPolicy: UserTopicVisibilityPolicy.followed),
60+
).throws<AssertionError>();
61+
});
62+
});
63+
64+
test('policy: none -> op: remove', () {
65+
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
66+
connection.prepare(json: {});
67+
await updateUserTopicCompat(connection,
68+
streamId: 1, topic: 'topic',
69+
visibilityPolicy: UserTopicVisibilityPolicy.none);
70+
check(connection.takeRequests()).single.isA<http.Request>()
71+
..method.equals('PATCH')
72+
..url.path.equals('/api/v1/users/me/subscriptions/muted_topics')
73+
..bodyFields.deepEquals({
74+
'stream_id': '1',
75+
'topic': 'topic',
76+
'op': 'remove',
77+
});
78+
});
79+
});
80+
81+
test('policy: muted -> op: add', () {
82+
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
83+
connection.prepare(json: {});
84+
await updateUserTopicCompat(connection,
85+
streamId: 1, topic: 'topic',
86+
visibilityPolicy: UserTopicVisibilityPolicy.muted);
87+
check(connection.takeRequests()).single.isA<http.Request>()
88+
..method.equals('PATCH')
89+
..url.path.equals('/api/v1/users/me/subscriptions/muted_topics')
90+
..bodyFields.deepEquals({
91+
'stream_id': '1',
92+
'topic': 'topic',
93+
'op': 'add',
94+
});
95+
});
96+
});
97+
});
98+
}

0 commit comments

Comments
 (0)