Skip to content

Commit cc3dfb9

Browse files
committed
api: Take stream or PM conversation as parameter in sendMessage
1 parent e4c85e8 commit cc3dfb9

File tree

3 files changed

+84
-24
lines changed

3 files changed

+84
-24
lines changed

lib/api/route/messages.dart

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,26 +92,52 @@ const int kMaxMessageLengthCodePoints = 10000;
9292
const String kNoTopicTopic = '(no topic)';
9393

9494
/// https://zulip.com/api/send-message
95-
// TODO currently only handles stream messages; fix
9695
Future<SendMessageResult> sendMessage(
9796
ApiConnection connection, {
97+
required MessageDestination destination,
9898
required String content,
99-
required String topic,
10099
}) {
101-
// assert() is less verbose but would have no effect in production, I think:
102-
// https://dart.dev/guides/language/language-tour#assert
103-
if (connection.realmUrl.origin != 'https://chat.zulip.org') {
104-
throw Exception('This binding can currently only be used on https://chat.zulip.org.');
105-
}
106-
107100
return connection.post('sendMessage', SendMessageResult.fromJson, 'messages', {
108-
'type': RawParameter('stream'), // TODO parametrize
109-
'to': 7, // TODO parametrize; this is `#test here`
110-
'topic': RawParameter(topic),
101+
if (destination is StreamDestination) ...{
102+
'type': RawParameter('stream'),
103+
'to': destination.streamId,
104+
'topic': RawParameter(destination.topic),
105+
} else if (destination is PmDestination) ...{
106+
'type': RawParameter('private'), // TODO(server-7)
107+
'to': destination.userIds,
108+
} else ...(
109+
throw Exception('impossible destination') // TODO(dart-3) show this statically
110+
),
111111
'content': RawParameter(content),
112112
});
113113
}
114114

115+
/// Which conversation to send a message to, in [sendMessage].
116+
///
117+
/// This is either a [StreamDestination] or a [PmDestination].
118+
sealed class MessageDestination {}
119+
120+
/// A conversation in a stream, for specifying to [sendMessage].
121+
///
122+
/// The server accepts a stream name as an alternative to a stream ID,
123+
/// but this binding currently doesn't.
124+
class StreamDestination extends MessageDestination {
125+
StreamDestination(this.streamId, this.topic);
126+
127+
final int streamId;
128+
final String topic;
129+
}
130+
131+
/// A PM conversation, for specifying to [sendMessage].
132+
///
133+
/// The server accepts a list of Zulip API emails as an alternative to
134+
/// a list of user IDs, but this binding currently doesn't.
135+
class PmDestination extends MessageDestination {
136+
PmDestination({required this.userIds});
137+
138+
final List<int> userIds;
139+
}
140+
115141
@JsonSerializable(fieldRename: FieldRename.snake)
116142
class SendMessageResult {
117143
final int id;

lib/model/store.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,13 @@ class PerAccountStore extends ChangeNotifier {
251251
Future<void> sendStreamMessage({required String topic, required String content}) {
252252
// TODO implement outbox; see design at
253253
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/.23M3881.20Sending.20outbox.20messages.20is.20fraught.20with.20issues/near/1405739
254-
return sendMessage(connection, topic: topic, content: content);
254+
255+
if (connection.realmUrl.origin != 'https://chat.zulip.org') {
256+
throw Exception('This method can currently only be used on https://chat.zulip.org.');
257+
}
258+
final destination = StreamDestination(7, topic); // TODO parametrize; this is `#test here`
259+
260+
return sendMessage(connection, destination: destination, content: content);
255261
}
256262
}
257263

test/api/route/messages_test.dart

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,52 @@
1+
import 'dart:convert';
2+
13
import 'package:checks/checks.dart';
4+
import 'package:http/http.dart' as http;
25
import 'package:test/scaffolding.dart';
36
import 'package:zulip/api/route/messages.dart';
47

8+
import '../../stdlib_checks.dart';
59
import '../fake_api.dart';
610
import 'route_checks.dart';
711

812
void main() {
9-
test('sendMessage accepts fixture realm', () async {
10-
final connection = FakeApiConnection(
11-
realmUrl: Uri.parse('https://chat.zulip.org/'));
12-
connection.prepare(json: SendMessageResult(id: 42).toJson());
13-
check(sendMessage(connection, content: 'hello', topic: 'world'))
14-
.completes(it()..id.equals(42));
13+
test('sendMessage to stream', () {
14+
return FakeApiConnection.with_((connection) async {
15+
const streamId = 123;
16+
const topic = 'world';
17+
const content = 'hello';
18+
connection.prepare(json: SendMessageResult(id: 42).toJson());
19+
final result = await sendMessage(connection,
20+
destination: StreamDestination(streamId, topic), content: content);
21+
check(result).id.equals(42);
22+
check(connection.lastRequest).isNotNull().isA<http.Request>()
23+
..method.equals('POST')
24+
..url.path.equals('/api/v1/messages')
25+
..bodyFields.deepEquals({
26+
'type': 'stream',
27+
'to': streamId.toString(),
28+
'topic': topic,
29+
'content': content,
30+
});
31+
});
1532
});
1633

17-
test('sendMessage rejects unexpected realm', () async {
18-
final connection = FakeApiConnection(
19-
realmUrl: Uri.parse('https://chat.example/'));
20-
connection.prepare(json: SendMessageResult(id: 42).toJson());
21-
check(() => sendMessage(connection, content: 'hello', topic: 'world'))
22-
.throws();
34+
test('sendMessage to PM conversation', () {
35+
return FakeApiConnection.with_((connection) async {
36+
const userIds = [23, 34];
37+
const content = 'hi there';
38+
connection.prepare(json: SendMessageResult(id: 42).toJson());
39+
final result = await sendMessage(connection,
40+
destination: PmDestination(userIds: userIds), content: content);
41+
check(result).id.equals(42);
42+
check(connection.lastRequest).isNotNull().isA<http.Request>()
43+
..method.equals('POST')
44+
..url.path.equals('/api/v1/messages')
45+
..bodyFields.deepEquals({
46+
'type': 'private',
47+
'to': jsonEncode(userIds),
48+
'content': content,
49+
});
50+
});
2351
});
2452
}

0 commit comments

Comments
 (0)