message-type: Add support for "channel" as value for type parameter.

For endpoints with a type parameter to indicate whether a message is
a direct or stream message, adds support for passing "channel" as a
value for stream messages.

Part of stream to channel rename project.
This commit is contained in:
Lauryn Menard 2024-04-10 20:48:10 +02:00 committed by Tim Abbott
parent d24dadb52f
commit 01b59c5aa2
10 changed files with 188 additions and 92 deletions

View File

@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 9.0 ## Changes in Zulip 9.0
**Feature level 248**
* [`POST /typing`](/api/set-typing-status), [`POST /messages`](/api/send-message),
[`POST /scheduled_messages`](/api/create-scheduled-message),
[`PATCH /scheduled_messages/<int:scheduled_message_id>`](/api/update-scheduled-message):
Added `"channel"` as an additional value for the `type` parameter to
indicate a stream message.
**Feature level 247** **Feature level 247**
* [Markdown message formatting](/api/message-formatting#mentions): * [Markdown message formatting](/api/message-formatting#mentions):

View File

@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in api_docs/changelog.md, as well as "**Changes**" # new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`. # entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 247 API_FEATURE_LEVEL = 248
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump # only when going from an old version of the code to a newer version. Bump

View File

@ -129,7 +129,7 @@ class Message(AbstractMessage):
# deprecating the original "private" and becoming the # deprecating the original "private" and becoming the
# preferred way to indicate a personal or huddle # preferred way to indicate a personal or huddle
# Recipient type via the API. # Recipient type via the API.
API_RECIPIENT_TYPES = ["direct", "private", "stream"] API_RECIPIENT_TYPES = ["direct", "private", "stream", "channel"]
search_tsvector = SearchVectorField(null=True) search_tsvector = SearchVectorField(null=True)

View File

@ -5677,17 +5677,20 @@ paths:
type: type:
description: | description: |
The type of scheduled message to be sent. `"direct"` for a direct The type of scheduled message to be sent. `"direct"` for a direct
message and `"stream"` for a stream message. message and `"stream"` or `"channel"` for a stream message.
In Zulip 7.0 (feature level 174), `"direct"` was added as the Note that, while `"private"` is supported for scheduling direct
preferred way to indicate the type of a direct message, deprecating messages, clients are encouraged to use to the modern convention of
the original `"private"`. While `"private"` is supported for `"direct"` to indicate this message type, because support for
scheduling direct messages, clients are encouraged to use to the `"private"` may eventually be removed.
modern convention because support for `"private"` may eventually
be removed. **Changes**: In Zulip 9.0 (feature level 248), `"channel"` was added as
an additional value for this parameter to indicate the type of a stream
message.
type: string type: string
enum: enum:
- direct - direct
- channel
- stream - stream
- private - private
example: direct example: direct
@ -5822,21 +5825,24 @@ paths:
type: type:
description: | description: |
The type of scheduled message to be sent. `"direct"` for a direct The type of scheduled message to be sent. `"direct"` for a direct
message and `"stream"` for a stream message. message and `"stream"` or `"channel"` for a stream message.
When updating the type of the scheduled message, the `to` parameter When updating the type of the scheduled message, the `to` parameter
is required. And, if updating the type of the scheduled message to is required. And, if updating the type of the scheduled message to
`"stream"`, then the `topic` parameter is also required. `"stream"`/`"channel"`, then the `topic` parameter is also required.
In Zulip 7.0 (feature level 174), `"direct"` was added as the Note that, while `"private"` is supported for scheduling direct
preferred way to indicate the type of a direct message, deprecating messages, clients are encouraged to use to the modern convention of
the original `"private"`. While `"private"` is supported for `"direct"` to indicate this message type, because support for
scheduling direct messages, clients are encouraged to use to the `"private"` may eventually be removed.
modern convention because support for `"private"` may eventually
be removed. **Changes**: In Zulip 9.0 (feature level 248), `"channel"` was added as
an additional value for this parameter to indicate the type of a stream
message.
type: string type: string
enum: enum:
- direct - direct
- channel
- stream - stream
- private - private
example: stream example: stream
@ -6452,9 +6458,13 @@ paths:
description: | description: |
The type of message to be sent. The type of message to be sent.
`"direct"` for a direct message and `"stream"` for a stream message. `"direct"` for a direct message and `"stream"` or `"channel"` for a
stream message.
**Changes**: In Zulip 7.0 (feature level 174), `"direct"` was added as **Changes**: In Zulip 9.0 (feature level 248), `"channel"` was added as
an additional value for this parameter to request a stream message.
In Zulip 7.0 (feature level 174), `"direct"` was added as
the preferred way to request a direct message, deprecating the original the preferred way to request a direct message, deprecating the original
`"private"`. While `"private"` is still supported for requesting direct `"private"`. While `"private"` is still supported for requesting direct
messages, clients are encouraged to use to the modern convention with messages, clients are encouraged to use to the modern convention with
@ -6463,6 +6473,7 @@ paths:
type: string type: string
enum: enum:
- direct - direct
- channel
- stream - stream
- private - private
example: direct example: direct
@ -18069,7 +18080,11 @@ paths:
description: | description: |
Type of the message being composed. Type of the message being composed.
**Changes**: In Zulip 8.0 (feature level 215), stopped supporting **Changes**: In Zulip 9.0 (feature level 248), `"channel"` was added as
an additional value for this parameter to indicate a stream message is
being composed.
In Zulip 8.0 (feature level 215), stopped supporting
`"private"` as a valid value for this parameter. `"private"` as a valid value for this parameter.
In Zulip 7.0 (feature level 174), `"direct"` was added In Zulip 7.0 (feature level 174), `"direct"` was added
@ -18083,6 +18098,7 @@ paths:
enum: enum:
- direct - direct
- stream - stream
- channel
default: direct default: direct
example: direct example: direct
op: op:

View File

@ -84,34 +84,40 @@ class MessagePOSTTest(ZulipTestCase):
Sending a message to a stream to which you are subscribed is Sending a message to a stream to which you are subscribed is
successful. successful.
""" """
recipient_type_name = ["stream", "channel"]
self.login("hamlet") self.login("hamlet")
result = self.client_post(
"/json/messages", for recipient_type in recipient_type_name:
{ result = self.client_post(
"type": "stream", "/json/messages",
"to": orjson.dumps("Verona").decode(), {
"content": "Test message", "type": recipient_type,
"topic": "Test topic", "to": orjson.dumps("Verona").decode(),
}, "content": "Test message",
) "topic": "Test topic",
self.assert_json_success(result) },
)
self.assert_json_success(result)
def test_api_message_to_stream_by_name(self) -> None: def test_api_message_to_stream_by_name(self) -> None:
""" """
Same as above, but for the API view Same as above, but for the API view
""" """
recipient_type_name = ["stream", "channel"]
user = self.example_user("hamlet") user = self.example_user("hamlet")
result = self.api_post(
user, for recipient_type in recipient_type_name:
"/api/v1/messages", result = self.api_post(
{ user,
"type": "stream", "/api/v1/messages",
"to": orjson.dumps("Verona").decode(), {
"content": "Test message", "type": recipient_type,
"topic": "Test topic", "to": orjson.dumps("Verona").decode(),
}, "content": "Test message",
) "topic": "Test topic",
self.assert_json_success(result) },
)
self.assert_json_success(result)
def test_message_to_stream_with_nonexistent_id(self) -> None: def test_message_to_stream_with_nonexistent_id(self) -> None:
cordelia = self.example_user("cordelia") cordelia = self.example_user("cordelia")
@ -123,7 +129,7 @@ class MessagePOSTTest(ZulipTestCase):
bot, bot,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": orjson.dumps([99999]).decode(), "to": orjson.dumps([99999]).decode(),
"content": "Stream message by ID.", "content": "Stream message by ID.",
"topic": "Test topic for stream ID message", "topic": "Test topic for stream ID message",
@ -154,7 +160,7 @@ class MessagePOSTTest(ZulipTestCase):
bot, bot,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": orjson.dumps(stream.name).decode(), "to": orjson.dumps(stream.name).decode(),
"content": "Stream message to an empty stream by name.", "content": "Stream message to an empty stream by name.",
"topic": "Test topic for empty stream name message", "topic": "Test topic for empty stream name message",
@ -189,7 +195,7 @@ class MessagePOSTTest(ZulipTestCase):
bot, bot,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": orjson.dumps([stream.id]).decode(), "to": orjson.dumps([stream.id]).decode(),
"content": "Stream message to an empty stream by id.", "content": "Stream message to an empty stream by id.",
"topic": "Test topic for empty stream id message", "topic": "Test topic for empty stream id message",
@ -213,21 +219,25 @@ class MessagePOSTTest(ZulipTestCase):
Sending a message to a stream (by stream ID) to which you are Sending a message to a stream (by stream ID) to which you are
subscribed is successful. subscribed is successful.
""" """
recipient_type_name = ["stream", "channel"]
self.login("hamlet") self.login("hamlet")
realm = get_realm("zulip") realm = get_realm("zulip")
stream = get_stream("Verona", realm) stream = get_stream("Verona", realm)
result = self.client_post(
"/json/messages", for recipient_type in recipient_type_name:
{ content = f"Stream message by ID, type parameter: {recipient_type}."
"type": "stream", result = self.client_post(
"to": orjson.dumps([stream.id]).decode(), "/json/messages",
"content": "Stream message by ID.", {
"topic": "Test topic for stream ID message", "type": recipient_type,
}, "to": orjson.dumps([stream.id]).decode(),
) "content": content,
self.assert_json_success(result) "topic": "Test topic for stream ID message",
sent_message = self.get_last_message() },
self.assertEqual(sent_message.content, "Stream message by ID.") )
self.assert_json_success(result)
sent_message = self.get_last_message()
self.assertEqual(sent_message.content, content)
def test_sending_message_as_stream_post_policy_admins(self) -> None: def test_sending_message_as_stream_post_policy_admins(self) -> None:
""" """
@ -522,7 +532,7 @@ class MessagePOSTTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"content": "Test message no to", "content": "Test message no to",
"topic": "Test topic", "topic": "Test topic",
}, },
@ -542,7 +552,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "stream", "type": "channel",
"to": "nonexistent_stream", "to": "nonexistent_stream",
"content": "Test message", "content": "Test message",
"topic": "Test topic", "topic": "Test topic",
@ -559,7 +569,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "stream", "type": "channel",
"to": """&<"'><non-existent>""", "to": """&<"'><non-existent>""",
"content": "Test message", "content": "Test message",
"topic": "Test topic", "topic": "Test topic",
@ -585,7 +595,7 @@ class MessagePOSTTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": orjson.dumps("Verona").decode(), "to": orjson.dumps("Verona").decode(),
"content": "Test message", "content": "Test message",
"topic": "Test topic", "topic": "Test topic",
@ -601,7 +611,7 @@ class MessagePOSTTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": orjson.dumps("Verona").decode(), "to": orjson.dumps("Verona").decode(),
"content": "Another Test message", "content": "Another Test message",
"topic": "Test topic", "topic": "Test topic",
@ -882,7 +892,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "stream", "type": "channel",
"to": "Verona", "to": "Verona",
"content": "Test message", "content": "Test message",
"topic": "", "topic": "",
@ -897,7 +907,7 @@ class MessagePOSTTest(ZulipTestCase):
self.login("hamlet") self.login("hamlet")
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{"type": "stream", "to": "Verona", "content": "Test message"}, {"type": "channel", "to": "Verona", "content": "Test message"},
) )
self.assert_json_error(result, "Missing topic") self.assert_json_error(result, "Missing topic")
@ -910,7 +920,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "stream", "type": "channel",
"to": "Verona", "to": "Verona",
"topic": "Test\n\rTopic", "topic": "Test\n\rTopic",
"content": "Test message", "content": "Test message",
@ -922,7 +932,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "stream", "type": "channel",
"to": "Verona", "to": "Verona",
"topic": "Test\ufffeTopic", "topic": "Test\ufffeTopic",
"content": "Test message", "content": "Test message",
@ -932,7 +942,7 @@ class MessagePOSTTest(ZulipTestCase):
def test_invalid_recipient_type(self) -> None: def test_invalid_recipient_type(self) -> None:
""" """
Messages other than the type of "direct", "private" or "stream" are invalid. Messages other than the type of "direct", "private", "channel" or "stream" are invalid.
""" """
self.login("hamlet") self.login("hamlet")
result = self.client_post( result = self.client_post(
@ -1072,7 +1082,7 @@ class MessagePOSTTest(ZulipTestCase):
""" """
self.login("hamlet") self.login("hamlet")
post_data = { post_data = {
"type": "stream", "type": "channel",
"to": "Verona", "to": "Verona",
"content": " I like null bytes \x00 in my content", "content": " I like null bytes \x00 in my content",
"topic": "Test topic", "topic": "Test topic",
@ -1086,7 +1096,7 @@ class MessagePOSTTest(ZulipTestCase):
""" """
self.login("hamlet") self.login("hamlet")
post_data = { post_data = {
"type": "stream", "type": "channel",
"to": orjson.dumps("Verona").decode(), "to": orjson.dumps("Verona").decode(),
"content": " I like whitespace at the end! \n\n \n", "content": " I like whitespace at the end! \n\n \n",
"topic": "Test topic", "topic": "Test topic",
@ -1098,7 +1108,7 @@ class MessagePOSTTest(ZulipTestCase):
# Test if it removes the new line from the beginning of the message. # Test if it removes the new line from the beginning of the message.
post_data = { post_data = {
"type": "stream", "type": "channel",
"to": orjson.dumps("Verona").decode(), "to": orjson.dumps("Verona").decode(),
"content": "\nAvoid the new line at the beginning of the message.", "content": "\nAvoid the new line at the beginning of the message.",
"topic": "Test topic", "topic": "Test topic",
@ -1120,7 +1130,7 @@ class MessagePOSTTest(ZulipTestCase):
MAX_MESSAGE_LENGTH = settings.MAX_MESSAGE_LENGTH MAX_MESSAGE_LENGTH = settings.MAX_MESSAGE_LENGTH
long_message = "A" * (MAX_MESSAGE_LENGTH + 1) long_message = "A" * (MAX_MESSAGE_LENGTH + 1)
post_data = { post_data = {
"type": "stream", "type": "channel",
"to": orjson.dumps("Verona").decode(), "to": orjson.dumps("Verona").decode(),
"content": long_message, "content": long_message,
"topic": "Test topic", "topic": "Test topic",
@ -1141,7 +1151,7 @@ class MessagePOSTTest(ZulipTestCase):
self.login("hamlet") self.login("hamlet")
long_topic_name = "A" * (MAX_TOPIC_NAME_LENGTH + 1) long_topic_name = "A" * (MAX_TOPIC_NAME_LENGTH + 1)
post_data = { post_data = {
"type": "stream", "type": "channel",
"to": orjson.dumps("Verona").decode(), "to": orjson.dumps("Verona").decode(),
"content": "test content", "content": "test content",
"topic": long_topic_name, "topic": long_topic_name,
@ -1157,7 +1167,7 @@ class MessagePOSTTest(ZulipTestCase):
result = self.client_post( result = self.client_post(
"/json/messages", "/json/messages",
{ {
"type": "stream", "type": "channel",
"to": "Verona", "to": "Verona",
"content": "Test message", "content": "Test message",
"topic": "Test topic", "topic": "Test topic",
@ -1185,7 +1195,7 @@ class MessagePOSTTest(ZulipTestCase):
self.mit_user("starnine"), self.mit_user("starnine"),
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"content": "Test message", "content": "Test message",
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -1282,7 +1292,7 @@ class MessagePOSTTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"forged": "true", "forged": "true",
"time": fake_timestamp, "time": fake_timestamp,
"sender": "irc-user@irc.zulip.com", "sender": "irc-user@irc.zulip.com",
@ -1305,7 +1315,7 @@ class MessagePOSTTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"forged": "yes", "forged": "yes",
"time": fake_timestamp, "time": fake_timestamp,
"sender": "irc-user@irc.zulip.com", "sender": "irc-user@irc.zulip.com",
@ -1335,7 +1345,7 @@ class MessagePOSTTest(ZulipTestCase):
def test_with(sender_email: str, client: str, forged: bool) -> None: def test_with(sender_email: str, client: str, forged: bool) -> None:
payload = dict( payload = dict(
type="stream", type="channel",
to=orjson.dumps(stream_name).decode(), to=orjson.dumps(stream_name).decode(),
client=client, client=client,
topic="whatever", topic="whatever",
@ -1382,7 +1392,7 @@ class MessagePOSTTest(ZulipTestCase):
self.make_stream(stream_name, invite_only=True) self.make_stream(stream_name, invite_only=True)
payload = dict( payload = dict(
type="stream", type="channel",
to=orjson.dumps(stream_name).decode(), to=orjson.dumps(stream_name).decode(),
topic="whatever", topic="whatever",
content="whatever", content="whatever",
@ -1408,7 +1418,7 @@ class MessagePOSTTest(ZulipTestCase):
notification_bot, notification_bot,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": orjson.dumps("notify_channel").decode(), "to": orjson.dumps("notify_channel").decode(),
"content": "Test message", "content": "Test message",
"topic": "Test topic", "topic": "Test topic",
@ -1429,7 +1439,7 @@ class MessagePOSTTest(ZulipTestCase):
stream_name = "public stream" stream_name = "public stream"
self.make_stream(stream_name, invite_only=False) self.make_stream(stream_name, invite_only=False)
payload = dict( payload = dict(
type="stream", type="channel",
to=orjson.dumps(stream_name).decode(), to=orjson.dumps(stream_name).decode(),
topic="whatever", topic="whatever",
content="whatever", content="whatever",
@ -2211,7 +2221,7 @@ class StreamMessagesTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": orjson.dumps("Verona").decode(), "to": orjson.dumps("Verona").decode(),
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"client": "zephyr_mirror", "client": "zephyr_mirror",
@ -2228,7 +2238,7 @@ class StreamMessagesTest(ZulipTestCase):
user, user,
"/api/v1/messages", "/api/v1/messages",
{ {
"type": "stream", "type": "channel",
"to": "Verona", "to": "Verona",
"sender": self.mit_email("sipbtest"), "sender": self.mit_email("sipbtest"),
"client": "zephyr_mirror", "client": "zephyr_mirror",

View File

@ -40,7 +40,7 @@ class ScheduledMessageTest(ZulipTestCase):
self.login("hamlet") self.login("hamlet")
topic_name = "" topic_name = ""
if msg_type == "stream": if msg_type in ["stream", "channel"]:
topic_name = "Test topic" topic_name = "Test topic"
payload = { payload = {
@ -61,7 +61,7 @@ class ScheduledMessageTest(ZulipTestCase):
# Scheduling a message to a stream you are subscribed is successful. # Scheduling a message to a stream you are subscribed is successful.
result = self.do_schedule_message( result = self.do_schedule_message(
"stream", verona_stream_id, content + " 1", scheduled_delivery_timestamp "channel", verona_stream_id, content + " 1", scheduled_delivery_timestamp
) )
scheduled_message = self.last_scheduled_message() scheduled_message = self.last_scheduled_message()
self.assert_json_success(result) self.assert_json_success(result)
@ -99,7 +99,7 @@ class ScheduledMessageTest(ZulipTestCase):
scheduled_delivery_timestamp = int(scheduled_delivery_datetime.timestamp()) scheduled_delivery_timestamp = int(scheduled_delivery_datetime.timestamp())
verona_stream_id = self.get_stream_id("Verona") verona_stream_id = self.get_stream_id("Verona")
result = self.do_schedule_message( result = self.do_schedule_message(
"stream", verona_stream_id, content + " 1", scheduled_delivery_timestamp "channel", verona_stream_id, content + " 1", scheduled_delivery_timestamp
) )
self.assert_json_success(result) self.assert_json_success(result)
@ -420,7 +420,7 @@ class ScheduledMessageTest(ZulipTestCase):
scheduled_delivery_timestamp = int(time.time() - 86400) scheduled_delivery_timestamp = int(time.time() - 86400)
result = self.do_schedule_message( result = self.do_schedule_message(
"stream", verona_stream_id, content + " 1", scheduled_delivery_timestamp "channel", verona_stream_id, content + " 1", scheduled_delivery_timestamp
) )
self.assert_json_error(result, "Scheduled delivery time must be in the future.") self.assert_json_error(result, "Scheduled delivery time must be in the future.")
@ -431,7 +431,7 @@ class ScheduledMessageTest(ZulipTestCase):
# Scheduling a message to a stream you are subscribed is successful. # Scheduling a message to a stream you are subscribed is successful.
result = self.do_schedule_message( result = self.do_schedule_message(
"stream", verona_stream_id, content, scheduled_delivery_timestamp "channel", verona_stream_id, content, scheduled_delivery_timestamp
) )
scheduled_message = self.last_scheduled_message() scheduled_message = self.last_scheduled_message()
self.assert_json_success(result) self.assert_json_success(result)
@ -445,6 +445,26 @@ class ScheduledMessageTest(ZulipTestCase):
scheduled_message_id = scheduled_message.id scheduled_message_id = scheduled_message.id
payload: Dict[str, Any] payload: Dict[str, Any]
# Edit message with other stream message type ("stream") and no other changes
# results in no changes to the scheduled message.
payload = {
"type": "stream",
"to": orjson.dumps(verona_stream_id).decode(),
"topic": "Test topic",
}
result = self.client_patch(f"/json/scheduled_messages/{scheduled_message_id}", payload)
self.assert_json_success(result)
scheduled_message = self.get_scheduled_message(str(scheduled_message_id))
self.assertEqual(scheduled_message.recipient.type, Recipient.STREAM)
self.assertEqual(scheduled_message.stream_id, verona_stream_id)
self.assertEqual(scheduled_message.content, "Original test message")
self.assertEqual(scheduled_message.topic_name(), "Test topic")
self.assertEqual(
scheduled_message.scheduled_timestamp,
timestamp_to_datetime(scheduled_delivery_timestamp),
)
# Sending request with only scheduled message ID returns an error # Sending request with only scheduled message ID returns an error
result = self.client_patch(f"/json/scheduled_messages/{scheduled_message_id}") result = self.client_patch(f"/json/scheduled_messages/{scheduled_message_id}")
self.assert_json_error(result, "Nothing to change") self.assert_json_error(result, "Nothing to change")
@ -480,7 +500,7 @@ class ScheduledMessageTest(ZulipTestCase):
# Trying to edit `type` to stream message type without a `topic` returns an error # Trying to edit `type` to stream message type without a `topic` returns an error
payload = { payload = {
"type": "stream", "type": "channel",
"to": orjson.dumps(verona_stream_id).decode(), "to": orjson.dumps(verona_stream_id).decode(),
} }
result = self.client_patch(f"/json/scheduled_messages/{scheduled_message_id}", payload) result = self.client_patch(f"/json/scheduled_messages/{scheduled_message_id}", payload)
@ -490,7 +510,7 @@ class ScheduledMessageTest(ZulipTestCase):
# Edit message `type` to stream with valid `to` and `topic` succeeds # Edit message `type` to stream with valid `to` and `topic` succeeds
payload = { payload = {
"type": "stream", "type": "channel",
"to": orjson.dumps(verona_stream_id).decode(), "to": orjson.dumps(verona_stream_id).decode(),
"topic": "New test topic", "topic": "New test topic",
} }
@ -568,7 +588,7 @@ class ScheduledMessageTest(ZulipTestCase):
verona_stream_id = self.get_stream_id("Verona") verona_stream_id = self.get_stream_id("Verona")
content = "Test message" content = "Test message"
scheduled_delivery_timestamp = int(time.time() + 86400) scheduled_delivery_timestamp = int(time.time() + 86400)
self.do_schedule_message("stream", verona_stream_id, content, scheduled_delivery_timestamp) self.do_schedule_message("channel", verona_stream_id, content, scheduled_delivery_timestamp)
# Single scheduled message # Single scheduled message
result = self.client_get("/json/scheduled_messages") result = self.client_get("/json/scheduled_messages")
@ -611,7 +631,7 @@ class ScheduledMessageTest(ZulipTestCase):
verona_stream_id = self.get_stream_id("Verona") verona_stream_id = self.get_stream_id("Verona")
scheduled_delivery_timestamp = int(time.time() + 86400) scheduled_delivery_timestamp = int(time.time() + 86400)
self.do_schedule_message("stream", verona_stream_id, content, scheduled_delivery_timestamp) self.do_schedule_message("channel", verona_stream_id, content, scheduled_delivery_timestamp)
scheduled_message = self.last_scheduled_message() scheduled_message = self.last_scheduled_message()
self.logout() self.logout()
@ -649,7 +669,7 @@ class ScheduledMessageTest(ZulipTestCase):
scheduled_delivery_timestamp = int(time.time() + 86400) scheduled_delivery_timestamp = int(time.time() + 86400)
# Test sending with attachment # Test sending with attachment
self.do_schedule_message("stream", verona_stream_id, content, scheduled_delivery_timestamp) self.do_schedule_message("channel", verona_stream_id, content, scheduled_delivery_timestamp)
scheduled_message = self.last_scheduled_message() scheduled_message = self.last_scheduled_message()
self.assertEqual( self.assertEqual(
list(attachment_object1.scheduled_messages.all().values_list("id", flat=True)), list(attachment_object1.scheduled_messages.all().values_list("id", flat=True)),

View File

@ -367,6 +367,26 @@ class TypingHappyPathTestDirectMessages(ZulipTestCase):
class TypingHappyPathTestStreams(ZulipTestCase): class TypingHappyPathTestStreams(ZulipTestCase):
def test_valid_type_and_op_parameters(self) -> None:
recipient_type_name = ["channel", "stream"]
operator_type = ["start", "stop"]
sender = self.example_user("hamlet")
stream_name = self.get_streams(sender)[0]
stream_id = self.get_stream_id(stream_name)
topic_name = "Some topic"
for recipient_type in recipient_type_name:
for operator in operator_type:
params = dict(
type=recipient_type,
op=operator,
stream_id=str(stream_id),
topic=topic_name,
)
result = self.api_post(sender, "/api/v1/typing", params)
self.assert_json_success(result)
def test_start(self) -> None: def test_start(self) -> None:
sender = self.example_user("hamlet") sender = self.example_user("hamlet")
stream_name = self.get_streams(sender)[0] stream_name = self.get_streams(sender)[0]

View File

@ -145,6 +145,11 @@ def send_message_backend(
# TODO: Use "direct" here, as well as in events and # TODO: Use "direct" here, as well as in events and
# message (created, schdeduled, drafts) objects/dicts. # message (created, schdeduled, drafts) objects/dicts.
recipient_type_name = "private" recipient_type_name = "private"
elif recipient_type_name == "channel":
# For now, use "stream" from Message.API_RECIPIENT_TYPES.
# TODO: Use "channel" here, as well as in events and
# message (created, schdeduled, drafts) objects/dicts.
recipient_type_name = "stream"
# If req_to is None, then we default to an # If req_to is None, then we default to an
# empty list of recipients. # empty list of recipients.

View File

@ -66,6 +66,12 @@ def update_scheduled_message_backend(
else: else:
recipient_type_name = req_type recipient_type_name = req_type
if recipient_type_name is not None and recipient_type_name == "channel":
# For now, use "stream" from Message.API_RECIPIENT_TYPES.
# TODO: Use "channel" here, as well as in events and
# message (created, schdeduled, drafts) objects/dicts.
recipient_type_name = "stream"
if recipient_type_name is not None and recipient_type_name == "stream" and topic_name is None: if recipient_type_name is not None and recipient_type_name == "stream" and topic_name is None:
raise JsonableError(_("Topic required when updating scheduled message type to stream.")) raise JsonableError(_("Topic required when updating scheduled message type to stream."))
@ -123,6 +129,11 @@ def create_scheduled_message_backend(
# TODO: Use "direct" here, as well as in events and # TODO: Use "direct" here, as well as in events and
# scheduled message objects/dicts. # scheduled message objects/dicts.
recipient_type_name = "private" recipient_type_name = "private"
elif recipient_type_name == "channel":
# For now, use "stream" from Message.API_RECIPIENT_TYPES.
# TODO: Use "channel" here, as well as in events and
# message (created, schdeduled, drafts) objects/dicts.
recipient_type_name = "stream"
deliver_at = timestamp_to_datetime(scheduled_delivery_timestamp) deliver_at = timestamp_to_datetime(scheduled_delivery_timestamp)
if deliver_at <= timezone_now(): if deliver_at <= timezone_now():

View File

@ -12,7 +12,7 @@ from zerver.lib.validator import check_int, check_list, check_string_in
from zerver.models import UserProfile from zerver.models import UserProfile
VALID_OPERATOR_TYPES = ["start", "stop"] VALID_OPERATOR_TYPES = ["start", "stop"]
VALID_RECIPIENT_TYPES = ["direct", "stream"] VALID_RECIPIENT_TYPES = ["direct", "stream", "channel"]
@has_request_variables @has_request_variables
@ -30,6 +30,12 @@ def send_notification_backend(
topic: Optional[str] = REQ("topic", default=None), topic: Optional[str] = REQ("topic", default=None),
) -> HttpResponse: ) -> HttpResponse:
recipient_type_name = req_type recipient_type_name = req_type
if recipient_type_name == "channel":
# For now, use "stream" from Message.API_RECIPIENT_TYPES.
# TODO: Use "channel" here, as well as in events and
# message (created, schdeduled, drafts) objects/dicts.
recipient_type_name = "stream"
if recipient_type_name == "stream": if recipient_type_name == "stream":
if stream_id is None: if stream_id is None:
raise JsonableError(_("Missing stream_id")) raise JsonableError(_("Missing stream_id"))