2023-05-07 20:04:37 +02:00
import re
2023-04-20 04:26:41 +02:00
import time
2023-11-19 19:45:19 +01:00
from datetime import timedelta
2023-05-07 20:04:37 +02:00
from io import StringIO
2024-07-12 02:30:23 +02:00
from typing import TYPE_CHECKING , Any
2023-04-17 14:11:08 +02:00
import orjson
2023-07-08 14:29:35 +02:00
import time_machine
2023-05-09 19:59:03 +02:00
from django . utils . timezone import now as timezone_now
2023-04-17 14:11:08 +02:00
2023-05-09 20:45:42 +02:00
from zerver . actions . scheduled_messages import (
2023-05-09 19:59:03 +02:00
SCHEDULED_MESSAGE_LATE_CUTOFF_MINUTES ,
try_deliver_one_scheduled_message ,
2023-05-09 20:45:42 +02:00
)
2023-05-09 19:59:03 +02:00
from zerver . actions . users import change_user_is_active
2023-04-17 14:11:08 +02:00
from zerver . lib . test_classes import ZulipTestCase
2023-05-11 18:12:03 +02:00
from zerver . lib . test_helpers import most_recent_message
2023-04-20 04:26:41 +02:00
from zerver . lib . timestamp import timestamp_to_datetime
2023-05-16 21:18:09 +02:00
from zerver . models import Attachment , Message , Recipient , ScheduledMessage , UserMessage
2023-04-17 14:11:08 +02:00
if TYPE_CHECKING :
from django . test . client import _MonkeyPatchedWSGIResponse as TestHttpResponse
class ScheduledMessageTest ( ZulipTestCase ) :
def last_scheduled_message ( self ) - > ScheduledMessage :
return ScheduledMessage . objects . all ( ) . order_by ( " -id " ) [ 0 ]
def get_scheduled_message ( self , id : str ) - > ScheduledMessage :
return ScheduledMessage . objects . get ( id = id )
def do_schedule_message (
self ,
msg_type : str ,
2024-07-12 02:30:23 +02:00
to : int | list [ str ] | list [ int ] ,
2023-04-17 14:11:08 +02:00
msg : str ,
2023-04-20 04:26:41 +02:00
scheduled_delivery_timestamp : int ,
2023-04-17 14:11:08 +02:00
) - > " TestHttpResponse " :
self . login ( " hamlet " )
topic_name = " "
2024-04-10 20:48:10 +02:00
if msg_type in [ " stream " , " channel " ] :
2023-04-17 14:11:08 +02:00
topic_name = " Test topic "
payload = {
" type " : msg_type ,
" to " : orjson . dumps ( to ) . decode ( ) ,
" content " : msg ,
" topic " : topic_name ,
2023-04-20 04:26:41 +02:00
" scheduled_delivery_timestamp " : scheduled_delivery_timestamp ,
2023-04-17 14:11:08 +02:00
}
2023-04-20 04:26:41 +02:00
result = self . client_post ( " /json/scheduled_messages " , payload )
2023-04-17 14:11:08 +02:00
return result
def test_schedule_message ( self ) - > None :
content = " Test message "
2023-04-20 04:26:41 +02:00
scheduled_delivery_timestamp = int ( time . time ( ) + 86400 )
verona_stream_id = self . get_stream_id ( " Verona " )
2023-04-17 14:11:08 +02:00
# Scheduling a message to a stream you are subscribed is successful.
result = self . do_schedule_message (
2024-04-10 20:48:10 +02:00
" channel " , verona_stream_id , content + " 1 " , scheduled_delivery_timestamp
2023-04-17 14:11:08 +02:00
)
2023-04-20 04:26:41 +02:00
scheduled_message = self . last_scheduled_message ( )
2023-04-17 14:11:08 +02:00
self . assert_json_success ( result )
2023-04-20 04:26:41 +02:00
self . assertEqual ( scheduled_message . content , " Test message 1 " )
self . assertEqual ( scheduled_message . rendered_content , " <p>Test message 1</p> " )
self . assertEqual ( scheduled_message . topic_name ( ) , " Test topic " )
self . assertEqual (
scheduled_message . scheduled_timestamp ,
timestamp_to_datetime ( scheduled_delivery_timestamp ) ,
)
2023-04-17 14:11:08 +02:00
2023-05-18 14:02:16 +02:00
# Scheduling a direct message with user IDs is successful.
2023-04-17 14:11:08 +02:00
othello = self . example_user ( " othello " )
result = self . do_schedule_message (
2023-05-09 20:45:42 +02:00
" direct " , [ othello . id ] , content + " 3 " , scheduled_delivery_timestamp
2023-04-17 14:11:08 +02:00
)
2023-04-20 04:26:41 +02:00
scheduled_message = self . last_scheduled_message ( )
2023-04-17 14:11:08 +02:00
self . assert_json_success ( result )
2023-04-20 04:26:41 +02:00
self . assertEqual ( scheduled_message . content , " Test message 3 " )
self . assertEqual ( scheduled_message . rendered_content , " <p>Test message 3</p> " )
self . assertEqual (
scheduled_message . scheduled_timestamp ,
timestamp_to_datetime ( scheduled_delivery_timestamp ) ,
2023-04-17 14:11:08 +02:00
)
2023-05-18 14:02:16 +02:00
# Cannot schedule a direct message with user emails.
result = self . do_schedule_message (
" direct " , [ othello . email ] , content + " 4 " , scheduled_delivery_timestamp
)
2024-08-18 14:16:43 +02:00
self . assert_json_error ( result , ' to[ " int " ] is not an integer ' )
2023-05-18 14:02:16 +02:00
2023-05-09 19:59:03 +02:00
def create_scheduled_message ( self ) - > None :
content = " Test message "
2023-11-19 19:45:19 +01:00
scheduled_delivery_datetime = timezone_now ( ) + timedelta ( minutes = 5 )
2023-05-09 19:59:03 +02:00
scheduled_delivery_timestamp = int ( scheduled_delivery_datetime . timestamp ( ) )
verona_stream_id = self . get_stream_id ( " Verona " )
result = self . do_schedule_message (
2024-04-10 20:48:10 +02:00
" channel " , verona_stream_id , content + " 1 " , scheduled_delivery_timestamp
2023-05-09 19:59:03 +02:00
)
self . assert_json_success ( result )
def test_successful_deliver_stream_scheduled_message ( self ) - > None :
# No scheduled message
2024-10-04 22:05:05 +02:00
result = try_deliver_one_scheduled_message ( )
2023-05-09 19:59:03 +02:00
self . assertFalse ( result )
self . create_scheduled_message ( )
scheduled_message = self . last_scheduled_message ( )
# mock current time to be greater than the scheduled time, so that the `scheduled_message` can be sent.
2023-11-19 19:45:19 +01:00
more_than_scheduled_delivery_datetime = scheduled_message . scheduled_timestamp + timedelta (
minutes = 1
2023-05-09 19:59:03 +02:00
)
2023-07-08 14:29:35 +02:00
2024-10-04 22:05:05 +02:00
with (
time_machine . travel ( more_than_scheduled_delivery_datetime , tick = False ) ,
self . assertLogs ( level = " INFO " ) as logs ,
) :
result = try_deliver_one_scheduled_message ( )
2023-07-08 14:29:35 +02:00
self . assertTrue ( result )
2024-10-04 22:05:05 +02:00
self . assertEqual (
logs . output ,
[
f " INFO:root:Sending scheduled message { scheduled_message . id } with date { scheduled_message . scheduled_timestamp } (sender: { scheduled_message . sender_id } ) "
] ,
2023-07-08 14:29:35 +02:00
)
scheduled_message . refresh_from_db ( )
assert isinstance ( scheduled_message . delivered_message_id , int )
self . assertEqual ( scheduled_message . delivered , True )
self . assertEqual ( scheduled_message . failed , False )
delivered_message = Message . objects . get ( id = scheduled_message . delivered_message_id )
self . assertEqual ( delivered_message . content , scheduled_message . content )
self . assertEqual ( delivered_message . rendered_content , scheduled_message . rendered_content )
self . assertEqual ( delivered_message . topic_name ( ) , scheduled_message . topic_name ( ) )
self . assertEqual ( delivered_message . date_sent , more_than_scheduled_delivery_datetime )
2023-05-09 19:59:03 +02:00
2023-05-18 13:54:01 +02:00
def test_successful_deliver_direct_scheduled_message ( self ) - > None :
2023-05-09 19:59:03 +02:00
# No scheduled message
2024-10-04 22:05:05 +02:00
self . assertFalse ( try_deliver_one_scheduled_message ( ) )
2023-05-09 19:59:03 +02:00
content = " Test message "
2023-11-19 19:45:19 +01:00
scheduled_delivery_datetime = timezone_now ( ) + timedelta ( minutes = 5 )
2023-05-09 19:59:03 +02:00
scheduled_delivery_timestamp = int ( scheduled_delivery_datetime . timestamp ( ) )
2023-05-13 01:15:08 +02:00
sender = self . example_user ( " hamlet " )
2023-05-09 19:59:03 +02:00
othello = self . example_user ( " othello " )
response = self . do_schedule_message (
" direct " , [ othello . id ] , content + " 3 " , scheduled_delivery_timestamp
)
self . assert_json_success ( response )
scheduled_message = self . last_scheduled_message ( )
# mock current time to be greater than the scheduled time.
2023-11-19 19:45:19 +01:00
more_than_scheduled_delivery_datetime = scheduled_delivery_datetime + timedelta ( minutes = 1 )
2023-07-08 14:29:35 +02:00
2024-10-04 22:05:05 +02:00
with (
time_machine . travel ( more_than_scheduled_delivery_datetime , tick = False ) ,
self . assertLogs ( level = " INFO " ) as logs ,
) :
result = try_deliver_one_scheduled_message ( )
2023-07-08 14:29:35 +02:00
self . assertTrue ( result )
2024-10-04 22:05:05 +02:00
self . assertEqual (
logs . output ,
[
f " INFO:root:Sending scheduled message { scheduled_message . id } with date { scheduled_message . scheduled_timestamp } (sender: { scheduled_message . sender_id } ) "
] ,
2023-07-08 14:29:35 +02:00
)
scheduled_message . refresh_from_db ( )
assert isinstance ( scheduled_message . delivered_message_id , int )
self . assertEqual ( scheduled_message . delivered , True )
self . assertEqual ( scheduled_message . failed , False )
delivered_message = Message . objects . get ( id = scheduled_message . delivered_message_id )
self . assertEqual ( delivered_message . content , scheduled_message . content )
self . assertEqual ( delivered_message . rendered_content , scheduled_message . rendered_content )
self . assertEqual ( delivered_message . date_sent , more_than_scheduled_delivery_datetime )
sender_user_message = UserMessage . objects . get (
message_id = scheduled_message . delivered_message_id , user_profile_id = sender . id
)
self . assertTrue ( sender_user_message . flags . read )
2023-05-09 19:59:03 +02:00
2023-05-11 17:33:42 +02:00
# Check error is sent if an edit happens after the scheduled
# message is successfully sent.
2023-11-19 19:45:19 +01:00
new_delivery_datetime = timezone_now ( ) + timedelta ( minutes = 7 )
2023-05-11 17:33:42 +02:00
new_delivery_timestamp = int ( new_delivery_datetime . timestamp ( ) )
2023-05-16 21:18:09 +02:00
content = " New message content "
payload = {
" content " : content ,
" scheduled_delivery_timestamp " : new_delivery_timestamp ,
}
updated_response = self . client_patch (
f " /json/scheduled_messages/ { scheduled_message . id } " , payload
2023-05-11 17:33:42 +02:00
)
self . assert_json_error ( updated_response , " Scheduled message was already sent " )
2023-05-18 13:54:01 +02:00
def test_successful_deliver_direct_scheduled_message_to_self ( self ) - > None :
2023-05-13 01:15:08 +02:00
# No scheduled message
2024-10-04 22:05:05 +02:00
self . assertFalse ( try_deliver_one_scheduled_message ( ) )
2023-05-13 01:15:08 +02:00
content = " Test message to self "
2023-11-19 19:45:19 +01:00
scheduled_delivery_datetime = timezone_now ( ) + timedelta ( minutes = 5 )
2023-05-13 01:15:08 +02:00
scheduled_delivery_timestamp = int ( scheduled_delivery_datetime . timestamp ( ) )
sender = self . example_user ( " hamlet " )
response = self . do_schedule_message (
" direct " , [ sender . id ] , content , scheduled_delivery_timestamp
)
self . assert_json_success ( response )
scheduled_message = self . last_scheduled_message ( )
# mock current time to be greater than the scheduled time.
2023-11-19 19:45:19 +01:00
more_than_scheduled_delivery_datetime = scheduled_delivery_datetime + timedelta ( minutes = 1 )
2023-07-08 14:29:35 +02:00
2024-10-04 22:05:05 +02:00
with (
time_machine . travel ( more_than_scheduled_delivery_datetime , tick = False ) ,
self . assertLogs ( level = " INFO " ) as logs ,
) :
result = try_deliver_one_scheduled_message ( )
2023-07-08 14:29:35 +02:00
self . assertTrue ( result )
2024-10-04 22:05:05 +02:00
self . assertEqual (
logs . output ,
[
f " INFO:root:Sending scheduled message { scheduled_message . id } with date { scheduled_message . scheduled_timestamp } (sender: { scheduled_message . sender_id } ) "
] ,
2023-07-08 14:29:35 +02:00
)
scheduled_message . refresh_from_db ( )
assert isinstance ( scheduled_message . delivered_message_id , int )
self . assertEqual ( scheduled_message . delivered , True )
self . assertEqual ( scheduled_message . failed , False )
delivered_message = Message . objects . get ( id = scheduled_message . delivered_message_id )
self . assertEqual ( delivered_message . content , scheduled_message . content )
self . assertEqual ( delivered_message . rendered_content , scheduled_message . rendered_content )
self . assertEqual ( delivered_message . date_sent , more_than_scheduled_delivery_datetime )
sender_user_message = UserMessage . objects . get (
message_id = scheduled_message . delivered_message_id , user_profile_id = sender . id
)
self . assertFalse ( sender_user_message . flags . read )
2023-05-13 01:15:08 +02:00
2023-05-09 19:59:03 +02:00
def verify_deliver_scheduled_message_failure (
2024-10-04 22:05:05 +02:00
self , scheduled_message : ScheduledMessage , expected_failure_message : str
2023-05-09 19:59:03 +02:00
) - > None :
2024-10-04 22:05:05 +02:00
with self . assertLogs ( level = " INFO " ) as logs :
result = try_deliver_one_scheduled_message ( )
2023-05-09 19:59:03 +02:00
self . assertTrue ( result )
scheduled_message . refresh_from_db ( )
self . assertEqual ( scheduled_message . failure_message , expected_failure_message )
2024-10-04 22:05:05 +02:00
self . assertEqual (
logs . output ,
[
f " INFO:root:Sending scheduled message { scheduled_message . id } with date { scheduled_message . scheduled_timestamp } (sender: { scheduled_message . sender_id } ) " ,
f " INFO:root:Failed with message: { scheduled_message . failure_message } " ,
] ,
)
2023-05-09 19:59:03 +02:00
self . assertTrue ( scheduled_message . failed )
def test_too_late_to_deliver_scheduled_message ( self ) - > None :
2023-05-11 18:12:03 +02:00
expected_failure_message = " Message could not be sent at the scheduled time. "
2023-05-09 19:59:03 +02:00
self . create_scheduled_message ( )
scheduled_message = self . last_scheduled_message ( )
2023-11-19 19:45:19 +01:00
too_late_to_send_message_datetime = scheduled_message . scheduled_timestamp + timedelta (
minutes = SCHEDULED_MESSAGE_LATE_CUTOFF_MINUTES + 1
2023-05-09 19:59:03 +02:00
)
2023-07-08 14:29:35 +02:00
with time_machine . travel ( too_late_to_send_message_datetime , tick = False ) :
2023-05-09 19:59:03 +02:00
self . verify_deliver_scheduled_message_failure (
2024-10-04 22:05:05 +02:00
scheduled_message , expected_failure_message
2023-05-09 19:59:03 +02:00
)
2023-05-11 18:12:03 +02:00
# Verify that the user was sent a message informing them about
# the failed scheduled message.
2023-08-10 09:32:02 +02:00
realm = scheduled_message . realm
2023-05-11 18:12:03 +02:00
msg = most_recent_message ( scheduled_message . sender )
self . assertEqual ( msg . recipient . type , msg . recipient . PERSONAL )
self . assertEqual ( msg . sender_id , self . notification_bot ( realm ) . id )
self . assertIn ( expected_failure_message , msg . content )
2023-05-09 19:59:03 +02:00
def test_realm_deactivated_failed_to_deliver_scheduled_message ( self ) - > None :
expected_failure_message = " This organization has been deactivated "
self . create_scheduled_message ( )
scheduled_message = self . last_scheduled_message ( )
2023-05-11 18:12:03 +02:00
# Verify realm isn't deactivated and get user's most recent
# message.
2023-08-10 09:32:02 +02:00
self . assertFalse ( scheduled_message . realm . deactivated )
2023-05-11 18:12:03 +02:00
message_before_deactivation = most_recent_message ( scheduled_message . sender )
2023-11-19 19:45:19 +01:00
more_than_scheduled_delivery_datetime = scheduled_message . scheduled_timestamp + timedelta (
minutes = 1
2023-05-09 19:59:03 +02:00
)
2023-07-08 14:29:35 +02:00
with time_machine . travel ( more_than_scheduled_delivery_datetime , tick = False ) :
2023-05-09 19:59:03 +02:00
scheduled_message = self . last_scheduled_message ( )
scheduled_message . realm . deactivated = True
scheduled_message . realm . save ( )
self . verify_deliver_scheduled_message_failure (
2024-10-04 22:05:05 +02:00
scheduled_message , expected_failure_message
2023-05-09 19:59:03 +02:00
)
2023-05-11 18:12:03 +02:00
# Verify that no failed scheduled message notification was sent.
2023-08-10 09:32:02 +02:00
self . assertTrue ( scheduled_message . realm . deactivated )
2023-05-11 18:12:03 +02:00
message_after_deactivation = most_recent_message ( scheduled_message . sender )
self . assertEqual ( message_after_deactivation . content , message_before_deactivation . content )
self . assertNotIn ( expected_failure_message , message_after_deactivation . content )
2023-05-09 19:59:03 +02:00
def test_sender_deactivated_failed_to_deliver_scheduled_message ( self ) - > None :
expected_failure_message = " Account is deactivated "
self . create_scheduled_message ( )
scheduled_message = self . last_scheduled_message ( )
2023-05-11 18:12:03 +02:00
# Verify user isn't deactivated and get user's most recent
# message.
self . assertTrue ( scheduled_message . sender . is_active )
message_before_deactivation = most_recent_message ( scheduled_message . sender )
2023-11-19 19:45:19 +01:00
more_than_scheduled_delivery_datetime = scheduled_message . scheduled_timestamp + timedelta (
minutes = 1
2023-05-09 19:59:03 +02:00
)
2023-07-08 14:29:35 +02:00
with time_machine . travel ( more_than_scheduled_delivery_datetime , tick = False ) :
2023-05-09 19:59:03 +02:00
scheduled_message = self . last_scheduled_message ( )
change_user_is_active ( scheduled_message . sender , False )
self . verify_deliver_scheduled_message_failure (
2024-10-04 22:05:05 +02:00
scheduled_message , expected_failure_message
2023-05-09 19:59:03 +02:00
)
2023-05-11 18:12:03 +02:00
# Verify that no failed scheduled message notification was sent.
self . assertFalse ( scheduled_message . sender . is_active )
message_after_deactivation = most_recent_message ( scheduled_message . sender )
self . assertEqual ( message_after_deactivation . content , message_before_deactivation . content )
self . assertNotIn ( expected_failure_message , message_after_deactivation . content )
2023-05-09 19:59:03 +02:00
def test_delivery_type_reminder_failed_to_deliver_scheduled_message_unknown_exception (
self ,
) - > None :
self . create_scheduled_message ( )
scheduled_message = self . last_scheduled_message ( )
2023-11-19 19:45:19 +01:00
more_than_scheduled_delivery_datetime = scheduled_message . scheduled_timestamp + timedelta (
minutes = 1
2023-05-09 19:59:03 +02:00
)
2023-07-08 14:29:35 +02:00
with time_machine . travel ( more_than_scheduled_delivery_datetime , tick = False ) :
2023-05-09 19:59:03 +02:00
scheduled_message = self . last_scheduled_message ( )
scheduled_message . delivery_type = ScheduledMessage . REMIND
scheduled_message . save ( )
2024-10-04 22:05:05 +02:00
with self . assertLogs ( level = " INFO " ) as logs :
result = try_deliver_one_scheduled_message ( )
2023-05-09 19:59:03 +02:00
self . assertTrue ( result )
scheduled_message . refresh_from_db ( )
2024-10-04 22:05:05 +02:00
self . assert_length ( logs . output , 2 )
self . assertEqual (
logs . output [ 0 ] ,
f " INFO:root:Sending scheduled message { scheduled_message . id } with date { scheduled_message . scheduled_timestamp } (sender: { scheduled_message . sender_id } ) " ,
2023-05-09 19:59:03 +02:00
)
2024-10-04 22:05:05 +02:00
self . assertTrue (
logs . output [ 1 ] . startswith (
f " ERROR:root:Unexpected error sending scheduled message { scheduled_message . id } (sent: { scheduled_message . delivered } ) \n Traceback (most recent call last) "
)
2023-05-09 19:59:03 +02:00
)
self . assertTrue ( scheduled_message . failed )
2023-05-11 18:12:03 +02:00
# Verify that the user was sent a message informing them about
# the failed scheduled message.
2023-08-10 09:32:02 +02:00
realm = scheduled_message . realm
2023-05-11 18:12:03 +02:00
msg = most_recent_message ( scheduled_message . sender )
self . assertEqual ( msg . recipient . type , msg . recipient . PERSONAL )
self . assertEqual ( msg . sender_id , self . notification_bot ( realm ) . id )
self . assertIn ( " Internal server error " , msg . content )
2023-05-12 20:14:17 +02:00
def test_editing_failed_send_scheduled_message ( self ) - > None :
expected_failure_message = " Message could not be sent at the scheduled time. "
self . create_scheduled_message ( )
scheduled_message = self . last_scheduled_message ( )
2023-11-19 19:45:19 +01:00
too_late_to_send_message_datetime = scheduled_message . scheduled_timestamp + timedelta (
minutes = SCHEDULED_MESSAGE_LATE_CUTOFF_MINUTES + 1
2023-05-12 20:14:17 +02:00
)
2023-07-08 14:29:35 +02:00
with time_machine . travel ( too_late_to_send_message_datetime , tick = False ) :
2023-05-12 20:14:17 +02:00
self . verify_deliver_scheduled_message_failure (
2024-10-04 22:05:05 +02:00
scheduled_message , expected_failure_message
2023-05-12 20:14:17 +02:00
)
2023-05-16 21:18:09 +02:00
# After verifying the scheduled message failed to be sent:
# Confirm not updating the scheduled delivery timestamp for
# the scheduled message with that ID returns an error.
payload_without_timestamp = { " topic " : " Failed to send " }
response = self . client_patch (
f " /json/scheduled_messages/ { scheduled_message . id } " , payload_without_timestamp
)
self . assert_json_error ( response , " Scheduled delivery time must be in the future. " )
# Editing the scheduled message with that ID for a future time is
2023-05-12 20:14:17 +02:00
# successful and resets the `failed` and `failure_message` fields.
2023-11-19 19:45:19 +01:00
new_delivery_datetime = timezone_now ( ) + timedelta ( minutes = 60 )
2023-05-12 20:14:17 +02:00
new_delivery_timestamp = int ( new_delivery_datetime . timestamp ( ) )
scheduled_message_id = scheduled_message . id
2023-05-16 21:18:09 +02:00
payload_with_timestamp = {
" scheduled_delivery_timestamp " : new_delivery_timestamp ,
}
response = self . client_patch (
f " /json/scheduled_messages/ { scheduled_message . id } " , payload_with_timestamp
2023-05-12 20:14:17 +02:00
)
self . assert_json_success ( response )
scheduled_message = self . last_scheduled_message ( )
self . assertEqual ( scheduled_message . id , scheduled_message_id )
self . assertFalse ( scheduled_message . failed )
self . assertIsNone ( scheduled_message . failure_message )
2023-04-17 14:11:08 +02:00
def test_scheduling_in_past ( self ) - > None :
# Scheduling a message in past should fail.
content = " Test message "
2023-04-20 04:26:41 +02:00
verona_stream_id = self . get_stream_id ( " Verona " )
scheduled_delivery_timestamp = int ( time . time ( ) - 86400 )
2023-04-17 14:11:08 +02:00
2023-04-20 04:26:41 +02:00
result = self . do_schedule_message (
2024-04-10 20:48:10 +02:00
" channel " , verona_stream_id , content + " 1 " , scheduled_delivery_timestamp
2023-04-17 14:11:08 +02:00
)
2023-04-20 04:26:41 +02:00
self . assert_json_error ( result , " Scheduled delivery time must be in the future. " )
2023-04-17 14:11:08 +02:00
def test_edit_schedule_message ( self ) - > None :
content = " Original test message "
2023-04-20 04:26:41 +02:00
scheduled_delivery_timestamp = int ( time . time ( ) + 86400 )
verona_stream_id = self . get_stream_id ( " Verona " )
2023-04-17 14:11:08 +02:00
# Scheduling a message to a stream you are subscribed is successful.
2023-04-20 04:26:41 +02:00
result = self . do_schedule_message (
2024-04-10 20:48:10 +02:00
" channel " , verona_stream_id , content , scheduled_delivery_timestamp
2023-04-20 04:26:41 +02:00
)
scheduled_message = self . last_scheduled_message ( )
2023-04-17 14:11:08 +02:00
self . assert_json_success ( result )
2023-05-16 21:18:09 +02:00
self . assertEqual ( scheduled_message . recipient . type , Recipient . STREAM )
2023-04-20 04:26:41 +02:00
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 ) ,
)
2023-05-16 21:18:09 +02:00
scheduled_message_id = scheduled_message . id
2024-07-12 02:30:17 +02:00
payload : dict [ str , Any ]
2023-05-16 21:18:09 +02:00
2024-04-10 20:48:10 +02:00
# 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 ) ,
)
2023-05-16 21:18:09 +02:00
# Sending request with only scheduled message ID returns an error
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message_id } " )
self . assert_json_error ( result , " Nothing to change " )
# Trying to edit only message `type` returns an error
payload = {
" type " : " direct " ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message_id } " , payload )
self . assert_json_error (
result , " Recipient required when updating type of scheduled message. "
)
# Edit message `type` with valid `to` parameter succeeds
othello = self . example_user ( " othello " )
to = [ othello . id ]
payload = { " type " : " direct " , " to " : orjson . dumps ( to ) . decode ( ) }
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 . assertNotEqual ( scheduled_message . recipient . type , Recipient . STREAM )
# Trying to edit `topic` for direct message is ignored
payload = {
2023-06-19 16:26:12 +02:00
" topic " : " Direct message topic " ,
2023-05-16 21:18:09 +02:00
}
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 . topic_name ( ) , " " )
# Trying to edit `type` to stream message type without a `topic` returns an error
payload = {
2024-04-10 20:48:10 +02:00
" type " : " channel " ,
2023-05-16 21:18:09 +02:00
" to " : orjson . dumps ( verona_stream_id ) . decode ( ) ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message_id } " , payload )
self . assert_json_error (
2024-04-16 15:52:21 +02:00
result , " Topic required when updating scheduled message type to channel. "
2023-05-16 21:18:09 +02:00
)
# Edit message `type` to stream with valid `to` and `topic` succeeds
payload = {
2024-04-10 20:48:10 +02:00
" type " : " channel " ,
2023-05-16 21:18:09 +02:00
" to " : orjson . dumps ( verona_stream_id ) . decode ( ) ,
" topic " : " New 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 . topic_name ( ) , " New test topic " )
# Trying to edit with timestamp in the past returns an error
new_scheduled_delivery_timestamp = int ( time . time ( ) - 86400 )
payload = {
" scheduled_delivery_timestamp " : new_scheduled_delivery_timestamp ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message_id } " , payload )
self . assert_json_error ( result , " Scheduled delivery time must be in the future. " )
2023-04-17 14:11:08 +02:00
2023-05-16 21:18:09 +02:00
# Edit content and time of scheduled message succeeds
2023-04-17 14:11:08 +02:00
edited_content = " Edited test message "
2023-04-20 04:26:41 +02:00
new_scheduled_delivery_timestamp = scheduled_delivery_timestamp + int (
time . time ( ) + ( 3 * 86400 )
)
2023-05-16 21:18:09 +02:00
payload = {
" content " : edited_content ,
" scheduled_delivery_timestamp " : new_scheduled_delivery_timestamp ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message_id } " , payload )
self . assert_json_success ( result )
2023-04-17 14:11:08 +02:00
2023-05-16 21:18:09 +02:00
scheduled_message = self . get_scheduled_message ( str ( scheduled_message_id ) )
self . assertEqual ( scheduled_message . content , edited_content )
self . assertEqual ( scheduled_message . topic_name ( ) , " New test topic " )
self . assertEqual (
scheduled_message . scheduled_timestamp ,
timestamp_to_datetime ( new_scheduled_delivery_timestamp ) ,
2023-04-17 14:11:08 +02:00
)
2023-05-16 21:18:09 +02:00
# Edit topic and content of scheduled stream message
edited_content = " Final content edit for test "
payload = {
" topic " : " Another topic for test " ,
" content " : edited_content ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message . id } " , payload )
self . assert_json_success ( result )
2023-04-20 04:26:41 +02:00
scheduled_message = self . get_scheduled_message ( str ( scheduled_message . id ) )
2023-05-16 21:18:09 +02:00
self . assertEqual ( scheduled_message . content , edited_content )
self . assertEqual ( scheduled_message . topic_name ( ) , " Another topic for test " )
# Edit only topic of scheduled stream message
payload = {
" topic " : " Final topic for test " ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message . id } " , payload )
2023-04-17 14:11:08 +02:00
self . assert_json_success ( result )
2023-05-16 21:18:09 +02:00
scheduled_message = self . get_scheduled_message ( str ( scheduled_message . id ) )
self . assertEqual ( scheduled_message . recipient . type , Recipient . STREAM )
2023-04-20 04:26:41 +02:00
self . assertEqual ( scheduled_message . content , edited_content )
2023-05-16 21:18:09 +02:00
self . assertEqual ( scheduled_message . topic_name ( ) , " Final topic for test " )
2023-04-20 04:26:41 +02:00
self . assertEqual (
scheduled_message . scheduled_timestamp ,
timestamp_to_datetime ( new_scheduled_delivery_timestamp ) ,
)
2023-04-17 14:11:08 +02:00
def test_fetch_scheduled_messages ( self ) - > None :
self . login ( " hamlet " )
# No scheduled message
result = self . client_get ( " /json/scheduled_messages " )
self . assert_json_success ( result )
self . assert_length ( orjson . loads ( result . content ) [ " scheduled_messages " ] , 0 )
2023-04-20 04:26:41 +02:00
verona_stream_id = self . get_stream_id ( " Verona " )
2023-04-17 14:11:08 +02:00
content = " Test message "
2023-04-20 04:26:41 +02:00
scheduled_delivery_timestamp = int ( time . time ( ) + 86400 )
2024-04-10 20:48:10 +02:00
self . do_schedule_message ( " channel " , verona_stream_id , content , scheduled_delivery_timestamp )
2023-04-17 14:11:08 +02:00
# Single scheduled message
result = self . client_get ( " /json/scheduled_messages " )
self . assert_json_success ( result )
scheduled_messages = orjson . loads ( result . content ) [ " scheduled_messages " ]
self . assert_length ( scheduled_messages , 1 )
2023-04-21 12:48:29 +02:00
self . assertEqual (
scheduled_messages [ 0 ] [ " scheduled_message_id " ] , self . last_scheduled_message ( ) . id
)
2023-04-17 14:11:08 +02:00
self . assertEqual ( scheduled_messages [ 0 ] [ " content " ] , content )
2023-04-20 04:26:41 +02:00
self . assertEqual ( scheduled_messages [ 0 ] [ " to " ] , verona_stream_id )
2023-04-17 14:11:08 +02:00
self . assertEqual ( scheduled_messages [ 0 ] [ " type " ] , " stream " )
self . assertEqual ( scheduled_messages [ 0 ] [ " topic " ] , " Test topic " )
self . assertEqual (
2023-04-20 04:26:41 +02:00
scheduled_messages [ 0 ] [ " scheduled_delivery_timestamp " ] , scheduled_delivery_timestamp
2023-04-17 14:11:08 +02:00
)
othello = self . example_user ( " othello " )
result = self . do_schedule_message (
2023-05-09 20:45:42 +02:00
" direct " , [ othello . id ] , content + " 3 " , scheduled_delivery_timestamp
2023-04-17 14:11:08 +02:00
)
# Multiple scheduled messages
result = self . client_get ( " /json/scheduled_messages " )
self . assert_json_success ( result )
self . assert_length ( orjson . loads ( result . content ) [ " scheduled_messages " ] , 2 )
# Check if another user can access these scheduled messages.
self . logout ( )
self . login ( " othello " )
result = self . client_get ( " /json/scheduled_messages " )
self . assert_json_success ( result )
self . assert_length ( orjson . loads ( result . content ) [ " scheduled_messages " ] , 0 )
def test_delete_scheduled_messages ( self ) - > None :
self . login ( " hamlet " )
content = " Test message "
2023-04-20 04:26:41 +02:00
verona_stream_id = self . get_stream_id ( " Verona " )
scheduled_delivery_timestamp = int ( time . time ( ) + 86400 )
2024-04-10 20:48:10 +02:00
self . do_schedule_message ( " channel " , verona_stream_id , content , scheduled_delivery_timestamp )
2023-04-20 04:26:41 +02:00
scheduled_message = self . last_scheduled_message ( )
2023-04-17 14:11:08 +02:00
self . logout ( )
# Other user cannot delete it.
othello = self . example_user ( " othello " )
2023-04-20 04:26:41 +02:00
result = self . api_delete ( othello , f " /api/v1/scheduled_messages/ { scheduled_message . id } " )
2023-04-17 14:11:08 +02:00
self . assert_json_error ( result , " Scheduled message does not exist " , 404 )
self . login ( " hamlet " )
2023-04-20 04:26:41 +02:00
result = self . client_delete ( f " /json/scheduled_messages/ { scheduled_message . id } " )
2023-04-17 14:11:08 +02:00
self . assert_json_success ( result )
# Already deleted.
2023-04-20 04:26:41 +02:00
result = self . client_delete ( f " /json/scheduled_messages/ { scheduled_message . id } " )
2023-04-17 14:11:08 +02:00
self . assert_json_error ( result , " Scheduled message does not exist " , 404 )
2023-05-07 20:04:37 +02:00
def test_attachment_handling ( self ) - > None :
self . login ( " hamlet " )
hamlet = self . example_user ( " hamlet " )
verona_stream_id = self . get_stream_id ( " Verona " )
attachment_file1 = StringIO ( " zulip! " )
attachment_file1 . name = " dummy_1.txt "
result = self . client_post ( " /json/user_uploads " , { " file " : attachment_file1 } )
2024-07-15 07:06:38 +02:00
path_id1 = re . sub ( r " /user_uploads/ " , " " , result . json ( ) [ " url " ] )
2023-05-07 20:04:37 +02:00
attachment_object1 = Attachment . objects . get ( path_id = path_id1 )
attachment_file2 = StringIO ( " zulip! " )
attachment_file2 . name = " dummy_1.txt "
result = self . client_post ( " /json/user_uploads " , { " file " : attachment_file2 } )
2024-07-15 07:06:38 +02:00
path_id2 = re . sub ( r " /user_uploads/ " , " " , result . json ( ) [ " url " ] )
2023-05-07 20:04:37 +02:00
attachment_object2 = Attachment . objects . get ( path_id = path_id2 )
content = f " Test [zulip.txt](http:// { hamlet . realm . host } /user_uploads/ { path_id1 } ) "
scheduled_delivery_timestamp = int ( time . time ( ) + 86400 )
# Test sending with attachment
2024-04-10 20:48:10 +02:00
self . do_schedule_message ( " channel " , verona_stream_id , content , scheduled_delivery_timestamp )
2023-05-07 20:04:37 +02:00
scheduled_message = self . last_scheduled_message ( )
self . assertEqual (
list ( attachment_object1 . scheduled_messages . all ( ) . values_list ( " id " , flat = True ) ) ,
[ scheduled_message . id ] ,
)
self . assertEqual ( scheduled_message . has_attachment , True )
# Test editing to change attachmment
edited_content = f " Test [zulip.txt](http:// { hamlet . realm . host } /user_uploads/ { path_id2 } ) "
2023-05-16 21:18:09 +02:00
payload = {
" content " : edited_content ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message . id } " , payload )
2023-05-07 20:04:37 +02:00
scheduled_message = self . get_scheduled_message ( str ( scheduled_message . id ) )
self . assertEqual (
list ( attachment_object1 . scheduled_messages . all ( ) . values_list ( " id " , flat = True ) ) , [ ]
)
self . assertEqual (
list ( attachment_object2 . scheduled_messages . all ( ) . values_list ( " id " , flat = True ) ) ,
[ scheduled_message . id ] ,
)
self . assertEqual ( scheduled_message . has_attachment , True )
# Test editing to no longer reference any attachments
edited_content = " No more attachments "
2023-05-16 21:18:09 +02:00
payload = {
" content " : edited_content ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message . id } " , payload )
2023-05-07 20:04:37 +02:00
scheduled_message = self . get_scheduled_message ( str ( scheduled_message . id ) )
self . assertEqual (
list ( attachment_object1 . scheduled_messages . all ( ) . values_list ( " id " , flat = True ) ) , [ ]
)
self . assertEqual (
list ( attachment_object2 . scheduled_messages . all ( ) . values_list ( " id " , flat = True ) ) , [ ]
)
self . assertEqual ( scheduled_message . has_attachment , False )
# Test editing to now have an attachment again
edited_content = (
f " Attachment is back! [zulip.txt](http:// { hamlet . realm . host } /user_uploads/ { path_id2 } ) "
)
2023-05-16 21:18:09 +02:00
payload = {
" content " : edited_content ,
}
result = self . client_patch ( f " /json/scheduled_messages/ { scheduled_message . id } " , payload )
2023-05-07 20:04:37 +02:00
scheduled_message = self . get_scheduled_message ( str ( scheduled_message . id ) )
self . assertEqual (
list ( attachment_object1 . scheduled_messages . all ( ) . values_list ( " id " , flat = True ) ) , [ ]
)
self . assertEqual (
list ( attachment_object2 . scheduled_messages . all ( ) . values_list ( " id " , flat = True ) ) ,
[ scheduled_message . id ] ,
)
self . assertEqual ( scheduled_message . has_attachment , True )