Skip to content

Commit 868f8b7

Browse files
PIG208gnprice
authored andcommitted
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 1e04f7b commit 868f8b7

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

lib/api/route/channels.dart

Lines changed: 49 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,51 @@ 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+
final op = switch (visibilityPolicy) {
55+
UserTopicVisibilityPolicy.none => 'remove',
56+
UserTopicVisibilityPolicy.muted => 'add',
57+
_ => throw UnsupportedError('$visibilityPolicy on old server'),
58+
};
59+
// https://zulip.com/api/mute-topic
60+
return connection.patch('muteTopic', (_) {}, 'users/me/subscriptions/muted_topics', {
61+
'stream_id': streamId,
62+
'topic': RawParameter(topic),
63+
'op': RawParameter(op),
64+
});
65+
} else {
66+
return updateUserTopic(connection,
67+
streamId: streamId,
68+
topic: topic,
69+
visibilityPolicy: visibilityPolicy);
70+
}
71+
}
72+
73+
/// https://zulip.com/api/update-user-topic
74+
///
75+
/// This binding only supports feature levels 170+.
76+
// TODO(server-7) remove FL 170+ mention in doc, and the related `assert`
77+
Future<void> updateUserTopic(ApiConnection connection, {
78+
required int streamId,
79+
required String topic,
80+
required UserTopicVisibilityPolicy visibilityPolicy,
81+
}) {
82+
assert(visibilityPolicy != UserTopicVisibilityPolicy.unknown);
83+
assert(connection.zulipFeatureLevel! >= 170);
84+
return connection.post('updateUserTopic', (_) {}, 'user_topics', {
85+
'stream_id': streamId,
86+
'topic': RawParameter(topic),
87+
'visibility_policy': visibilityPolicy,
88+
});
89+
}

test/api/route/channels_test.dart

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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.muted),
60+
).throws<AssertionError>();
61+
});
62+
});
63+
64+
test('updateUserTopicCompat throws UnsupportedError on unsupported policy', () {
65+
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
66+
check(() => updateUserTopicCompat(connection,
67+
streamId: 1, topic: 'topic',
68+
visibilityPolicy: UserTopicVisibilityPolicy.followed),
69+
).throws<UnsupportedError>();
70+
});
71+
});
72+
73+
test('policy: none -> op: remove', () {
74+
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
75+
connection.prepare(json: {});
76+
await updateUserTopicCompat(connection,
77+
streamId: 1, topic: 'topic',
78+
visibilityPolicy: UserTopicVisibilityPolicy.none);
79+
check(connection.takeRequests()).single.isA<http.Request>()
80+
..method.equals('PATCH')
81+
..url.path.equals('/api/v1/users/me/subscriptions/muted_topics')
82+
..bodyFields.deepEquals({
83+
'stream_id': '1',
84+
'topic': 'topic',
85+
'op': 'remove',
86+
});
87+
});
88+
});
89+
90+
test('policy: muted -> op: add', () {
91+
return FakeApiConnection.with_(zulipFeatureLevel: 169, (connection) async {
92+
connection.prepare(json: {});
93+
await updateUserTopicCompat(connection,
94+
streamId: 1, topic: 'topic',
95+
visibilityPolicy: UserTopicVisibilityPolicy.muted);
96+
check(connection.takeRequests()).single.isA<http.Request>()
97+
..method.equals('PATCH')
98+
..url.path.equals('/api/v1/users/me/subscriptions/muted_topics')
99+
..bodyFields.deepEquals({
100+
'stream_id': '1',
101+
'topic': 'topic',
102+
'op': 'add',
103+
});
104+
});
105+
});
106+
});
107+
}

0 commit comments

Comments
 (0)