def test_topics_history(self) -> None: # verified: int(UserMessage.flags.read) == 1 user_profile = self.example_user("iago") self.login_user(user_profile) stream_name = "Verona" stream = get_stream(stream_name, user_profile.realm) recipient = stream.recipient def create_test_message(topic: str) -> int: # TODO: Clean this up to send messages the normal way. hamlet = self.example_user("hamlet") message = Message( sender=hamlet, recipient=recipient, content="whatever", date_sent=timezone_now(), sending_client=get_client("whatever"), ) message.set_topic_name(topic) message.save() UserMessage.objects.create( user_profile=user_profile, message=message, flags=0, ) return message.id # our most recent topics are topic0, topic1, topic2 # Create old messages with strange spellings. create_test_message("topic2") create_test_message("toPIc1") create_test_message("toPIc0") create_test_message("topic2") create_test_message("topic2") create_test_message("Topic2") # Create new messages topic2_msg_id = create_test_message("topic2") create_test_message("topic1") create_test_message("topic1") topic1_msg_id = create_test_message("topic1") topic0_msg_id = create_test_message("topic0") endpoint = f"/json/users/me/{stream.id}/topics" result = self.client_get(endpoint, {}) self.assert_json_success(result) history = result.json()["topics"] # We only look at the most recent three topics, because # the prior fixture data may be unreliable. history = history[:3] self.assertEqual( [topic["name"] for topic in history], [ "topic0", "topic1", "topic2", ], ) self.assertEqual( [topic["max_id"] for topic in history], [ topic0_msg_id, topic1_msg_id, topic2_msg_id, ], ) # Now try as cordelia, who we imagine as a totally new user in # that she doesn't have UserMessage rows. We should see the # same results for a public stream. self.login("cordelia") result = self.client_get(endpoint, {}) self.assert_json_success(result) history = result.json()["topics"] # We only look at the most recent three topics, because # the prior fixture data may be unreliable. history = history[:3] self.assertEqual( [topic["name"] for topic in history], [ "topic0", "topic1", "topic2", ], ) self.assertIn("topic0", [topic["name"] for topic in history]) self.assertEqual( [topic["max_id"] for topic in history], [ topic0_msg_id, topic1_msg_id, topic2_msg_id, ], ) # Now make stream private, but subscribe cordelia do_change_stream_permission( stream, invite_only=True, acting_user=self.example_user("cordelia") ) self.subscribe(self.example_user("cordelia"), stream.name) result = self.client_get(endpoint, {}) self.assert_json_success(result) history = result.json()["topics"] history = history[:3] # Cordelia doesn't have these recent history items when we # wasn't subscribed in her results. self.assertNotIn("topic0", [topic["name"] for topic in history]) self.assertNotIn("topic1", [topic["name"] for topic in history]) self.assertNotIn("topic2", [topic["name"] for topic in history])
def update_stream_backend( request: HttpRequest, user_profile: UserProfile, stream_id: int, description: Optional[str] = REQ(str_validator=check_capped_string( Stream.MAX_DESCRIPTION_LENGTH), default=None), is_private: Optional[bool] = REQ(json_validator=check_bool, default=None), is_announcement_only: Optional[bool] = REQ(json_validator=check_bool, default=None), stream_post_policy: Optional[int] = REQ(json_validator=check_int_in( Stream.STREAM_POST_POLICY_TYPES), default=None), history_public_to_subscribers: Optional[bool] = REQ( json_validator=check_bool, default=None), is_web_public: Optional[bool] = REQ(json_validator=check_bool, default=None), new_name: Optional[str] = REQ(default=None), message_retention_days: Optional[Union[int, str]] = REQ( json_validator=check_string_or_int, default=None), ) -> HttpResponse: # We allow realm administrators to to update the stream name and # description even for private streams. (stream, sub) = access_stream_for_delete_or_update(user_profile, stream_id) if message_retention_days is not None: if not user_profile.is_realm_owner: raise OrganizationOwnerRequired() user_profile.realm.ensure_not_on_limited_plan() new_message_retention_days_value = parse_message_retention_days( message_retention_days, Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP) do_change_stream_message_retention_days( stream, user_profile, new_message_retention_days_value) if description is not None: if "\n" in description: # We don't allow newline characters in stream descriptions. description = description.replace("\n", " ") do_change_stream_description(stream, description, acting_user=user_profile) if new_name is not None: new_name = new_name.strip() if stream.name == new_name: raise JsonableError(_("Stream already has that name!")) if stream.name.lower() != new_name.lower(): # Check that the stream name is available (unless we are # are only changing the casing of the stream name). check_stream_name_available(user_profile.realm, new_name) do_rename_stream(stream, new_name, user_profile) if is_announcement_only is not None: # is_announcement_only is a legacy way to specify # stream_post_policy. We can probably just delete this code, # since we're not aware of clients that used it, but we're # keeping it for backwards-compatibility for now. stream_post_policy = Stream.STREAM_POST_POLICY_EVERYONE if is_announcement_only: stream_post_policy = Stream.STREAM_POST_POLICY_ADMINS if stream_post_policy is not None: do_change_stream_post_policy(stream, stream_post_policy, acting_user=user_profile) # But we require even realm administrators to be actually # subscribed to make a private stream public. if is_private is not None: default_stream_ids = { s.id for s in get_default_streams_for_realm(stream.realm_id) } (stream, sub) = access_stream_by_id(user_profile, stream_id) if is_private and stream.id in default_stream_ids: raise JsonableError(_("Default streams cannot be made private.")) if is_web_public: # Enforce restrictions on creating web-public streams. if not user_profile.realm.web_public_streams_enabled(): raise JsonableError(_("Web-public streams are not enabled.")) if not user_profile.can_create_web_public_streams(): raise JsonableError(_("Insufficient permission")) # Forbid parameter combinations that are inconsistent if is_private or history_public_to_subscribers is False: raise JsonableError(_("Invalid parameters")) if is_private is not None or is_web_public is not None: do_change_stream_permission( stream, invite_only=is_private, history_public_to_subscribers=history_public_to_subscribers, is_web_public=is_web_public, acting_user=user_profile, ) return json_success(request)
def test_topic_delete(self) -> None: initial_last_msg_id = self.get_last_message().id stream_name = "new_stream" topic_name = "new topic 2" # NON-ADMIN USER user_profile = self.example_user("hamlet") self.subscribe(user_profile, stream_name) # Send message stream = get_stream(stream_name, user_profile.realm) self.send_stream_message(user_profile, stream_name, topic_name=topic_name) last_msg_id = self.send_stream_message(user_profile, stream_name, topic_name=topic_name) # Deleting the topic self.login_user(user_profile) endpoint = "/json/streams/" + str(stream.id) + "/delete_topic" result = self.client_post( endpoint, { "topic_name": topic_name, }, ) self.assert_json_error(result, "Must be an organization administrator") self.assertTrue(Message.objects.filter(id=last_msg_id).exists()) # Make stream private with limited history do_change_stream_permission( stream, invite_only=True, history_public_to_subscribers=False, acting_user=user_profile ) # ADMIN USER subscribed now user_profile = self.example_user("iago") self.subscribe(user_profile, stream_name) self.login_user(user_profile) new_last_msg_id = self.send_stream_message(user_profile, stream_name, topic_name=topic_name) # Now admin deletes all messages in topic -- which should only # delete new_last_msg_id, i.e. the one sent since they joined. self.assertEqual(self.get_last_message().id, new_last_msg_id) result = self.client_post( endpoint, { "topic_name": topic_name, }, ) self.assert_json_success(result) self.assertTrue(Message.objects.filter(id=last_msg_id).exists()) # Try to delete all messages in the topic again. There are no messages accessible # to the administrator, so this should do nothing. result = self.client_post( endpoint, { "topic_name": topic_name, }, ) self.assert_json_success(result) self.assertTrue(Message.objects.filter(id=last_msg_id).exists()) # Make the stream's history public to subscribers do_change_stream_permission( stream, invite_only=True, history_public_to_subscribers=True, acting_user=user_profile ) # Delete the topic should now remove all messages result = self.client_post( endpoint, { "topic_name": topic_name, }, ) self.assert_json_success(result) self.assertFalse(Message.objects.filter(id=last_msg_id).exists()) self.assertTrue(Message.objects.filter(id=initial_last_msg_id).exists()) # Delete again, to test the edge case of deleting an empty topic. result = self.client_post( endpoint, { "topic_name": topic_name, }, ) self.assert_json_success(result) self.assertFalse(Message.objects.filter(id=last_msg_id).exists()) self.assertTrue(Message.objects.filter(id=initial_last_msg_id).exists())
def test_reaction_event_scope(self) -> None: iago = self.example_user("iago") hamlet = self.example_user("hamlet") polonius = self.example_user("polonius") reaction_info = { "emoji_name": "smile", } # Test `invite_only` streams with `!history_public_to_subscribers` and `!is_web_public` stream = self.make_stream( "test_reactions_stream", invite_only=True, history_public_to_subscribers=False ) self.subscribe(iago, stream.name) message_before_id = self.send_stream_message( iago, "test_reactions_stream", "before subscription history private" ) self.subscribe(hamlet, stream.name) self.subscribe(polonius, stream.name) # Hamlet and Polonius joined after the message was sent, and # so only Iago should receive the event. events: List[Mapping[str, Any]] = [] with self.tornado_redirected_to_list(events, expected_num_events=1): result = self.api_post( iago, f"/api/v1/messages/{message_before_id}/reactions", reaction_info ) self.assert_json_success(result) event = events[0]["event"] self.assertEqual(event["type"], "reaction") event_user_ids = set(events[0]["users"]) self.assertEqual(event_user_ids, {iago.id}) remove = self.api_delete( iago, f"/api/v1/messages/{message_before_id}/reactions", reaction_info ) self.assert_json_success(remove) # Reaction to a Message sent after subscription, should # trigger events for all subscribers (Iago, Hamlet and Polonius). message_after_id = self.send_stream_message( iago, "test_reactions_stream", "after subscription history private" ) with self.tornado_redirected_to_list(events, expected_num_events=1): result = self.api_post( iago, f"/api/v1/messages/{message_after_id}/reactions", reaction_info ) self.assert_json_success(result) event = events[0]["event"] self.assertEqual(event["type"], "reaction") event_user_ids = set(events[0]["users"]) self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id}) remove = self.api_delete( iago, f"/api/v1/messages/{message_after_id}/reactions", reaction_info ) self.assert_json_success(remove) # Make stream history public to subscribers do_change_stream_permission( stream, invite_only=False, history_public_to_subscribers=True, acting_user=iago ) # Since stream history is public to subscribers, reacting to # message_before_id should notify all subscribers: # Iago and Hamlet. with self.tornado_redirected_to_list(events, expected_num_events=1): result = self.api_post( iago, f"/api/v1/messages/{message_before_id}/reactions", reaction_info ) self.assert_json_success(result) event = events[0]["event"] self.assertEqual(event["type"], "reaction") event_user_ids = set(events[0]["users"]) self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id}) remove = self.api_delete( iago, f"/api/v1/messages/{message_before_id}/reactions", reaction_info ) self.assert_json_success(remove) # Make stream web_public as well. do_change_stream_permission(stream, is_web_public=True, acting_user=iago) # For is_web_public streams, events even on old messages # should go to all subscribers, including guests like polonius. with self.tornado_redirected_to_list(events, expected_num_events=1): result = self.api_post( iago, f"/api/v1/messages/{message_before_id}/reactions", reaction_info ) self.assert_json_success(result) event = events[0]["event"] self.assertEqual(event["type"], "reaction") event_user_ids = set(events[0]["users"]) self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id}) remove = self.api_delete( iago, f"/api/v1/messages/{message_before_id}/reactions", reaction_info ) self.assert_json_success(remove) # Private message, event should go to both participants. private_message_id = self.send_personal_message( iago, hamlet, "hello to single receiver", ) with self.tornado_redirected_to_list(events, expected_num_events=1): result = self.api_post( hamlet, f"/api/v1/messages/{private_message_id}/reactions", reaction_info ) self.assert_json_success(result) event = events[0]["event"] self.assertEqual(event["type"], "reaction") event_user_ids = set(events[0]["users"]) self.assertEqual(event_user_ids, {iago.id, hamlet.id}) # Group private message; event should go to all participants. huddle_message_id = self.send_huddle_message( hamlet, [polonius, iago], "hello message to multiple receiver", ) with self.tornado_redirected_to_list(events, expected_num_events=1): result = self.api_post( polonius, f"/api/v1/messages/{huddle_message_id}/reactions", reaction_info ) self.assert_json_success(result) event = events[0]["event"] self.assertEqual(event["type"], "reaction") event_user_ids = set(events[0]["users"]) self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id})