2024-07-12 02:30:25 +02:00
|
|
|
from collections.abc import Iterable
|
|
|
|
from typing import Any, cast
|
2020-06-11 00:54:34 +02:00
|
|
|
from unittest import mock
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2023-10-12 19:43:45 +02:00
|
|
|
from typing_extensions import override
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2022-04-14 23:46:56 +02:00
|
|
|
from zerver.actions.custom_profile_fields import (
|
2020-06-11 00:54:34 +02:00
|
|
|
do_remove_realm_custom_profile_field,
|
|
|
|
do_update_user_custom_profile_data_if_changed,
|
|
|
|
try_add_realm_custom_profile_field,
|
|
|
|
try_reorder_realm_custom_profile_fields,
|
|
|
|
)
|
2021-10-26 09:15:16 +02:00
|
|
|
from zerver.actions.user_settings import do_change_user_setting
|
2019-08-24 13:52:25 +02:00
|
|
|
from zerver.lib.external_accounts import DEFAULT_EXTERNAL_ACCOUNTS
|
2020-07-04 14:34:46 +02:00
|
|
|
from zerver.lib.markdown import markdown_convert
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2022-07-08 16:53:36 +02:00
|
|
|
from zerver.lib.types import ProfileDataElementUpdateDict, ProfileDataElementValue
|
2023-12-15 20:57:08 +01:00
|
|
|
from zerver.models import CustomProfileField, CustomProfileFieldValue, UserProfile
|
|
|
|
from zerver.models.custom_profile_fields import custom_profile_fields_for_realm
|
2023-12-15 02:14:24 +01:00
|
|
|
from zerver.models.realms import get_realm
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2019-10-01 04:13:32 +02:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
class CustomProfileFieldTestCase(ZulipTestCase):
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2018-04-30 19:51:16 +02:00
|
|
|
def setUp(self) -> None:
|
2019-10-19 20:47:00 +02:00
|
|
|
super().setUp()
|
2018-05-06 11:35:34 +02:00
|
|
|
self.realm = get_realm("zulip")
|
|
|
|
self.original_count = len(custom_profile_fields_for_realm(self.realm.id))
|
|
|
|
|
|
|
|
def custom_field_exists_in_realm(self, field_id: int) -> bool:
|
|
|
|
fields = custom_profile_fields_for_realm(self.realm.id)
|
|
|
|
field_ids = [field.id for field in fields]
|
2021-02-12 08:19:30 +01:00
|
|
|
return field_id in field_ids
|
|
|
|
|
2018-04-30 19:51:16 +02:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_create(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2024-07-12 02:30:17 +02:00
|
|
|
data: dict[str, Any] = {"name": "Phone", "field_type": "text id"}
|
2017-03-17 10:07:22 +01:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2024-06-19 18:02:21 +02:00
|
|
|
self.assert_json_error(result, "field_type is not valid JSON")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
|
|
|
data["name"] = ""
|
|
|
|
data["field_type"] = 100
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Label cannot be blank.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-08-16 20:12:49 +02:00
|
|
|
data["name"] = "*" * 41
|
|
|
|
data["field_type"] = 100
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "name is too long (limit: 40 characters)")
|
2018-08-16 20:12:49 +02:00
|
|
|
|
2017-03-17 10:07:22 +01:00
|
|
|
data["name"] = "Phone"
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Invalid field type.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
|
|
|
data["name"] = "Phone"
|
2018-03-31 07:30:24 +02:00
|
|
|
data["hint"] = "*" * 81
|
|
|
|
data["field_type"] = CustomProfileField.SHORT_TEXT
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2022-07-12 21:04:47 +02:00
|
|
|
self.assert_json_error(result, "hint is too long (limit: 80 characters)")
|
2018-03-31 07:30:24 +02:00
|
|
|
|
|
|
|
data["name"] = "Phone"
|
|
|
|
data["hint"] = "Contact number"
|
2022-07-12 21:04:47 +02:00
|
|
|
data["field_type"] = CustomProfileField.LONG_TEXT
|
|
|
|
data["display_in_profile_summary"] = "true"
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, "Field type not supported for display in profile summary.")
|
|
|
|
|
|
|
|
data["field_type"] = CustomProfileField.USER
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, "Field type not supported for display in profile summary.")
|
|
|
|
|
2017-03-17 10:07:22 +01:00
|
|
|
data["field_type"] = CustomProfileField.SHORT_TEXT
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2018-04-08 18:13:37 +02:00
|
|
|
field = CustomProfileField.objects.get(name="Phone", realm=realm)
|
|
|
|
self.assertEqual(field.id, field.order)
|
|
|
|
|
2020-09-30 22:49:33 +02:00
|
|
|
data["name"] = "Name "
|
|
|
|
data["hint"] = "Some name"
|
|
|
|
data["field_type"] = CustomProfileField.SHORT_TEXT
|
2022-07-12 21:04:47 +02:00
|
|
|
data["display_in_profile_summary"] = "true"
|
2020-09-30 22:49:33 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
field = CustomProfileField.objects.get(name="Name", realm=realm)
|
|
|
|
self.assertEqual(field.id, field.order)
|
|
|
|
|
2022-07-12 21:04:47 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(
|
|
|
|
result, "Only 2 custom profile fields can be displayed in the profile summary."
|
|
|
|
)
|
|
|
|
|
|
|
|
data["display_in_profile_summary"] = "false"
|
2017-03-17 10:07:22 +01:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "A field with that label already exists.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2021-03-24 20:57:55 +01:00
|
|
|
def test_create_select_field(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
2024-07-12 02:30:23 +02:00
|
|
|
data: dict[str, str | int] = {}
|
2018-04-08 09:50:05 +02:00
|
|
|
data["name"] = "Favorite programming language"
|
2021-03-20 11:39:22 +01:00
|
|
|
data["field_type"] = CustomProfileField.SELECT
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
data["field_data"] = "invalid"
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2024-06-19 18:02:21 +02:00
|
|
|
error_msg = "field_data is not valid JSON"
|
2018-04-08 09:50:05 +02:00
|
|
|
self.assert_json_error(result, error_msg)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"python": ["1"],
|
|
|
|
"java": ["2"],
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2024-06-19 18:02:21 +02:00
|
|
|
self.assert_json_error(result, 'field_data["python"]["dict[str,str]"] is not a dict')
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2022-06-30 19:19:21 +02:00
|
|
|
"0": {"text": "Python"},
|
|
|
|
"1": {"text": "Java"},
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, "order key is missing from field_data")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2022-06-30 19:19:21 +02:00
|
|
|
"0": {"text": "Python", "order": ""},
|
|
|
|
"1": {"text": "Java", "order": "2"},
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, 'field_data["order"] cannot be blank.')
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"": {"text": "Python", "order": "1"},
|
2022-06-30 19:19:21 +02:00
|
|
|
"1": {"text": "Java", "order": "2"},
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, "'value' cannot be blank.")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2022-06-30 19:19:21 +02:00
|
|
|
"0": {"text": "Python", "order": 1},
|
|
|
|
"1": {"text": "Java", "order": "2"},
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2024-06-19 18:02:21 +02:00
|
|
|
self.assert_json_error(result, 'field_data["0"]["dict[str,str]"]["order"] is not a string')
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
data["field_data"] = orjson.dumps({}).decode()
|
2018-08-10 20:39:13 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Field must have at least one choice.")
|
2018-08-10 20:39:13 +02:00
|
|
|
|
2022-04-22 06:10:48 +02:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2022-06-30 19:19:21 +02:00
|
|
|
"0": {"text": "Duplicate", "order": "1"},
|
|
|
|
"1": {"text": "Duplicate", "order": "2"},
|
2022-04-22 06:10:48 +02:00
|
|
|
}
|
|
|
|
).decode()
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, "Field must not have duplicate choices.")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2022-06-30 19:19:21 +02:00
|
|
|
"0": {"text": "Python", "order": "1"},
|
|
|
|
"1": {"text": "Java", "order": "2"},
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2019-08-24 13:52:25 +02:00
|
|
|
def test_create_default_external_account_field(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
2019-08-24 13:52:25 +02:00
|
|
|
realm = get_realm("zulip")
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
field_type: int = CustomProfileField.EXTERNAL_ACCOUNT
|
2021-02-12 08:19:30 +01:00
|
|
|
field_data: str = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "twitter",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
invalid_field_name: str = "Not required field name"
|
|
|
|
invalid_field_hint: str = "Not required field hint"
|
2019-08-24 13:52:25 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_post(
|
|
|
|
"/json/realm/profile_fields",
|
|
|
|
info=dict(
|
|
|
|
field_type=field_type,
|
|
|
|
field_data=field_data,
|
|
|
|
hint=invalid_field_hint,
|
|
|
|
name=invalid_field_name,
|
|
|
|
),
|
|
|
|
)
|
2019-08-24 13:52:25 +02:00
|
|
|
self.assert_json_success(result)
|
2020-03-28 01:25:56 +01:00
|
|
|
# Silently overwrite name and hint with values set in default fields dict
|
2019-08-24 13:52:25 +02:00
|
|
|
# for default custom external account fields.
|
|
|
|
with self.assertRaises(CustomProfileField.DoesNotExist):
|
|
|
|
field = CustomProfileField.objects.get(name=invalid_field_name, realm=realm)
|
2022-09-12 20:05:05 +02:00
|
|
|
# The field is created with 'Twitter username' name as per values in default fields dict
|
|
|
|
field = CustomProfileField.objects.get(name="Twitter username")
|
2022-09-20 22:45:56 +02:00
|
|
|
self.assertEqual(field.name, DEFAULT_EXTERNAL_ACCOUNTS["twitter"].name)
|
|
|
|
self.assertEqual(field.hint, DEFAULT_EXTERNAL_ACCOUNTS["twitter"].hint)
|
2019-08-24 13:52:25 +02:00
|
|
|
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_delete(f"/json/realm/profile_fields/{field.id}")
|
2019-08-24 13:52:25 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
# Should also work without name or hint and only external field type and subtype data
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_post(
|
|
|
|
"/json/realm/profile_fields", info=dict(field_type=field_type, field_data=field_data)
|
|
|
|
)
|
2019-08-24 13:52:25 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2022-07-12 21:04:47 +02:00
|
|
|
# Default external account field data cannot be updated except "display_in_profile_summary" field
|
2022-09-12 20:05:05 +02:00
|
|
|
field = CustomProfileField.objects.get(name="Twitter username", realm=realm)
|
2019-08-24 13:52:25 +02:00
|
|
|
result = self.client_patch(
|
2020-06-09 00:25:09 +02:00
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
2022-09-12 20:05:05 +02:00
|
|
|
info={"name": "Twitter", "field_type": CustomProfileField.EXTERNAL_ACCOUNT},
|
2019-08-24 13:52:25 +02:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Default custom field cannot be updated.")
|
2019-08-24 13:52:25 +02:00
|
|
|
|
2022-07-12 21:04:47 +02:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"name": field.name,
|
|
|
|
"hint": field.hint,
|
|
|
|
"field_data": field_data,
|
|
|
|
"display_in_profile_summary": "true",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2020-06-09 00:25:09 +02:00
|
|
|
result = self.client_delete(f"/json/realm/profile_fields/{field.id}")
|
2019-08-24 13:52:25 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2019-05-27 10:59:55 +02:00
|
|
|
def test_create_external_account_field(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2024-07-12 02:30:23 +02:00
|
|
|
data: dict[str, str | int | dict[str, str]] = {}
|
2022-09-12 20:05:05 +02:00
|
|
|
data["name"] = "Twitter username"
|
2019-05-27 10:59:55 +02:00
|
|
|
data["field_type"] = CustomProfileField.EXTERNAL_ACCOUNT
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
data["field_data"] = "invalid"
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2024-06-19 18:02:21 +02:00
|
|
|
self.assert_json_error(result, "field_data is not valid JSON")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
data["field_data"] = orjson.dumps({}).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, "subtype key is missing from field_data")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, 'field_data["subtype"] cannot be blank.')
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "123",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Invalid external account type")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
non_default_external_account = "linkedin"
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": non_default_external_account,
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Invalid external account type")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "twitter",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2022-09-12 20:05:05 +02:00
|
|
|
twitter_field = CustomProfileField.objects.get(name="Twitter username", realm=realm)
|
2019-05-27 10:59:55 +02:00
|
|
|
self.assertEqual(twitter_field.field_type, CustomProfileField.EXTERNAL_ACCOUNT)
|
2022-09-12 20:05:05 +02:00
|
|
|
self.assertEqual(twitter_field.name, "Twitter username")
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(orjson.loads(twitter_field.field_data)["subtype"], "twitter")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
data["name"] = "Reddit"
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "custom",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Custom external account must define URL pattern")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "custom",
|
|
|
|
"url_pattern": 123,
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2024-06-19 18:02:21 +02:00
|
|
|
self.assert_json_error(result, 'field_data["url_pattern"]["dict[str,str]"] is not a dict')
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "custom",
|
|
|
|
"url_pattern": "invalid",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2022-04-29 17:08:57 +02:00
|
|
|
self.assert_json_error(result, "URL pattern must contain '%(username)s'.")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "custom",
|
|
|
|
"url_pattern": "https://www.reddit.com/%(username)s/user/%(username)s",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2022-04-29 17:08:57 +02:00
|
|
|
self.assert_json_error(result, "URL pattern must contain '%(username)s'.")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "custom",
|
|
|
|
"url_pattern": "reddit.com/%(username)s",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_error(result, 'field_data["url_pattern"] is not a URL')
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
data["field_data"] = orjson.dumps(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"subtype": "custom",
|
|
|
|
"url_pattern": "https://www.reddit.com/user/%(username)s",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
).decode()
|
2019-05-27 10:59:55 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
custom_field = CustomProfileField.objects.get(name="Reddit", realm=realm)
|
|
|
|
self.assertEqual(custom_field.field_type, CustomProfileField.EXTERNAL_ACCOUNT)
|
|
|
|
self.assertEqual(custom_field.name, "Reddit")
|
2020-08-07 01:09:47 +02:00
|
|
|
field_data = orjson.loads(custom_field.field_data)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(field_data["subtype"], "custom")
|
|
|
|
self.assertEqual(field_data["url_pattern"], "https://www.reddit.com/user/%(username)s")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
2019-08-03 02:30:15 +02:00
|
|
|
self.assert_json_error(result, "A field with that label already exists.")
|
2019-05-27 10:59:55 +02:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
def test_create_field_of_type_user(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
2021-02-12 08:19:30 +01:00
|
|
|
data = {
|
|
|
|
"name": "Your mentor",
|
|
|
|
"field_type": CustomProfileField.USER,
|
|
|
|
}
|
2019-08-24 15:10:49 +02:00
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2022-10-01 12:16:11 +02:00
|
|
|
def test_create_field_of_type_pronouns(self) -> None:
|
|
|
|
self.login("iago")
|
|
|
|
data = {
|
|
|
|
"name": "Pronouns for you",
|
|
|
|
"hint": "What pronouns should people use to refer to you?",
|
|
|
|
"field_type": CustomProfileField.PRONOUNS,
|
|
|
|
}
|
|
|
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_not_realm_admin(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("hamlet")
|
2017-03-17 10:07:22 +01:00
|
|
|
result = self.client_post("/json/realm/profile_fields")
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Must be an organization administrator")
|
2017-03-17 10:07:22 +01:00
|
|
|
result = self.client_delete("/json/realm/profile_fields/1")
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Must be an organization administrator")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
class DeleteCustomProfileFieldTest(CustomProfileFieldTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_delete(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2018-03-19 20:17:52 +01:00
|
|
|
field = CustomProfileField.objects.get(name="Phone number", realm=realm)
|
2017-03-17 10:07:22 +01:00
|
|
|
result = self.client_delete("/json/realm/profile_fields/100")
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Field id 100 not found.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-05-06 11:35:34 +02:00
|
|
|
self.assertTrue(self.custom_field_exists_in_realm(field.id))
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_delete(f"/json/realm/profile_fields/{field.id}")
|
2017-03-17 10:07:22 +01:00
|
|
|
self.assert_json_success(result)
|
2018-05-06 11:35:34 +02:00
|
|
|
self.assertFalse(self.custom_field_exists_in_realm(field.id))
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
def test_delete_field_value(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(iago)
|
2019-08-24 15:10:49 +02:00
|
|
|
realm = get_realm("zulip")
|
|
|
|
|
|
|
|
invalid_field_id = 1234
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_delete(
|
|
|
|
"/json/users/me/profile_data",
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"data": orjson.dumps([invalid_field_id]).decode(),
|
2021-02-12 08:19:30 +01:00
|
|
|
},
|
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, f"Field id {invalid_field_id} not found.")
|
2019-08-24 15:10:49 +02:00
|
|
|
|
|
|
|
field = CustomProfileField.objects.get(name="Mentor", realm=realm)
|
2024-07-12 02:30:17 +02:00
|
|
|
data: list[ProfileDataElementUpdateDict] = [
|
2021-02-12 08:20:45 +01:00
|
|
|
{"id": field.id, "value": [self.example_user("aaron").id]},
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
]
|
2019-10-01 04:22:50 +02:00
|
|
|
do_update_user_custom_profile_data_if_changed(iago, data)
|
2019-08-24 15:10:49 +02:00
|
|
|
|
|
|
|
iago_value = CustomProfileFieldValue.objects.get(user_profile=iago, field=field)
|
|
|
|
converter = field.FIELD_CONVERTERS[field.field_type]
|
|
|
|
self.assertEqual([self.example_user("aaron").id], converter(iago_value.value))
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_delete(
|
|
|
|
"/json/users/me/profile_data",
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"data": orjson.dumps([field.id]).decode(),
|
2021-02-12 08:19:30 +01:00
|
|
|
},
|
|
|
|
)
|
2019-08-24 15:10:49 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
# Don't throw an exception here
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_delete(
|
|
|
|
"/json/users/me/profile_data",
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"data": orjson.dumps([field.id]).decode(),
|
2021-02-12 08:19:30 +01:00
|
|
|
},
|
|
|
|
)
|
2019-08-24 15:10:49 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
def test_delete_internals(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
user_profile = self.example_user("iago")
|
2019-08-24 15:10:49 +02:00
|
|
|
realm = user_profile.realm
|
|
|
|
field = CustomProfileField.objects.get(name="Phone number", realm=realm)
|
2024-07-12 02:30:17 +02:00
|
|
|
data: list[ProfileDataElementUpdateDict] = [
|
2021-02-12 08:20:45 +01:00
|
|
|
{"id": field.id, "value": "123456"},
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
]
|
2019-10-01 04:22:50 +02:00
|
|
|
do_update_user_custom_profile_data_if_changed(user_profile, data)
|
2019-08-24 15:10:49 +02:00
|
|
|
|
|
|
|
self.assertTrue(self.custom_field_exists_in_realm(field.id))
|
|
|
|
self.assertEqual(user_profile.customprofilefieldvalue_set.count(), self.original_count)
|
|
|
|
|
|
|
|
do_remove_realm_custom_profile_field(realm, field)
|
|
|
|
|
|
|
|
self.assertFalse(self.custom_field_exists_in_realm(field.id))
|
|
|
|
self.assertEqual(user_profile.customprofilefieldvalue_set.count(), self.original_count - 1)
|
|
|
|
|
2024-08-15 16:38:12 +02:00
|
|
|
def test_delete_value_with_editable_by_user(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
realm = iago.realm
|
|
|
|
self.login("hamlet")
|
|
|
|
|
|
|
|
biography_custom_field = CustomProfileField.objects.get(name="Biography", realm=realm)
|
|
|
|
birthday_custom_field = CustomProfileField.objects.get(name="Birthday", realm=realm)
|
|
|
|
|
|
|
|
# Set and assert our initial state.
|
|
|
|
data = {}
|
|
|
|
data["editable_by_user"] = "false"
|
|
|
|
result = self.api_patch(
|
|
|
|
iago, f"/api/v1/realm/profile_fields/{birthday_custom_field.id}", info=data
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
birthday_custom_field.refresh_from_db()
|
|
|
|
self.assertFalse(birthday_custom_field.editable_by_user)
|
|
|
|
self.assertTrue(biography_custom_field.editable_by_user)
|
|
|
|
|
|
|
|
self.assertTrue(
|
|
|
|
CustomProfileFieldValue.objects.filter(
|
|
|
|
user_profile=iago, field=birthday_custom_field
|
|
|
|
).exists()
|
|
|
|
)
|
|
|
|
self.assertTrue(
|
|
|
|
CustomProfileFieldValue.objects.filter(
|
|
|
|
user_profile=hamlet, field=birthday_custom_field
|
|
|
|
).exists()
|
|
|
|
)
|
|
|
|
self.assertTrue(
|
|
|
|
CustomProfileFieldValue.objects.filter(
|
|
|
|
user_profile=hamlet, field=biography_custom_field
|
|
|
|
).exists()
|
|
|
|
)
|
|
|
|
|
|
|
|
# Users can only delete fields where editable_by_user is true.
|
|
|
|
result = self.client_delete(
|
|
|
|
"/json/users/me/profile_data",
|
|
|
|
{"data": orjson.dumps([biography_custom_field.id]).decode()},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.assertFalse(
|
|
|
|
CustomProfileFieldValue.objects.filter(
|
|
|
|
user_profile=hamlet, field=biography_custom_field
|
|
|
|
).exists()
|
|
|
|
)
|
|
|
|
|
|
|
|
result = self.client_delete(
|
|
|
|
"/json/users/me/profile_data",
|
|
|
|
{"data": orjson.dumps([birthday_custom_field.id]).decode()},
|
|
|
|
)
|
|
|
|
self.assert_json_error(
|
|
|
|
result,
|
|
|
|
"You are not allowed to change this field. Contact an administrator to update it.",
|
|
|
|
)
|
|
|
|
self.assertTrue(
|
|
|
|
CustomProfileFieldValue.objects.filter(
|
|
|
|
user_profile=hamlet, field=birthday_custom_field
|
|
|
|
).exists()
|
|
|
|
)
|
|
|
|
|
|
|
|
# Admins can always delete field values regardless of editable_by_user.
|
|
|
|
result = self.api_delete(
|
|
|
|
iago,
|
|
|
|
"/api/v1/users/me/profile_data",
|
|
|
|
{"data": orjson.dumps([birthday_custom_field.id]).decode()},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.assertFalse(
|
|
|
|
CustomProfileFieldValue.objects.filter(
|
|
|
|
user_profile=iago, field=birthday_custom_field
|
|
|
|
).exists()
|
|
|
|
)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_update(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2017-03-17 10:07:22 +01:00
|
|
|
result = self.client_patch(
|
|
|
|
"/json/realm/profile_fields/100",
|
2022-04-07 19:00:21 +02:00
|
|
|
info={"name": "Phone number"},
|
2017-03-17 10:07:22 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Field id 100 not found.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-03-19 20:17:52 +01:00
|
|
|
field = CustomProfileField.objects.get(name="Phone number", realm=realm)
|
2019-08-24 13:13:48 +02:00
|
|
|
result = self.client_patch(
|
2020-06-09 00:25:09 +02:00
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
2022-04-07 19:00:21 +02:00
|
|
|
info={"name": ""},
|
2019-08-24 13:13:48 +02:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Label cannot be blank.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-08-16 20:12:49 +02:00
|
|
|
result = self.client_patch(
|
2020-06-09 00:25:09 +02:00
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
2022-04-07 19:00:21 +02:00
|
|
|
info={"name": "*" * 41},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-08-16 20:12:49 +02:00
|
|
|
msg = "name is too long (limit: 40 characters)"
|
|
|
|
self.assert_json_error(result, msg)
|
|
|
|
|
2018-03-31 07:30:24 +02:00
|
|
|
result = self.client_patch(
|
2020-06-09 00:25:09 +02:00
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
2021-02-12 08:19:30 +01:00
|
|
|
info={
|
2021-02-12 08:20:45 +01:00
|
|
|
"hint": "*" * 81,
|
2021-02-12 08:19:30 +01:00
|
|
|
},
|
|
|
|
)
|
2018-05-03 23:21:16 +02:00
|
|
|
msg = "hint is too long (limit: 80 characters)"
|
2018-03-31 07:30:24 +02:00
|
|
|
self.assert_json_error(result, msg)
|
|
|
|
|
2024-03-19 14:22:03 +01:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"required": "invalid value",
|
|
|
|
},
|
|
|
|
)
|
2024-06-19 18:02:21 +02:00
|
|
|
msg = "required is not valid JSON"
|
2024-03-19 14:22:03 +01:00
|
|
|
self.assert_json_error(result, msg)
|
|
|
|
|
2024-08-15 16:38:12 +02:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"editable_by_user": "invalid value",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
msg = "editable_by_user is not valid JSON"
|
|
|
|
self.assert_json_error(result, msg)
|
|
|
|
|
2022-07-12 21:04:47 +02:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"name": "New phone number",
|
|
|
|
"hint": "New contact number",
|
|
|
|
"display_in_profile_summary": "true",
|
2024-03-19 14:22:03 +01:00
|
|
|
"required": "true",
|
2024-08-15 16:38:12 +02:00
|
|
|
"editable_by_user": "false",
|
2021-02-12 08:19:30 +01:00
|
|
|
},
|
|
|
|
)
|
2018-03-31 07:30:24 +02:00
|
|
|
self.assert_json_success(result)
|
2024-03-30 13:12:18 +01:00
|
|
|
field.refresh_from_db()
|
2018-04-30 19:51:16 +02:00
|
|
|
self.assertEqual(CustomProfileField.objects.count(), self.original_count)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(field.name, "New phone number")
|
|
|
|
self.assertEqual(field.hint, "New contact number")
|
2017-03-17 10:07:22 +01:00
|
|
|
self.assertEqual(field.field_type, CustomProfileField.SHORT_TEXT)
|
2022-07-12 21:04:47 +02:00
|
|
|
self.assertEqual(field.display_in_profile_summary, True)
|
2024-03-19 14:22:03 +01:00
|
|
|
self.assertEqual(field.required, True)
|
2024-08-15 16:38:12 +02:00
|
|
|
self.assertEqual(field.editable_by_user, False)
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2024-08-15 16:38:12 +02:00
|
|
|
# Not sending required or editable_by_user should not reset their value to default.
|
2024-03-30 13:12:18 +01:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"hint": "New hint",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
field.refresh_from_db()
|
|
|
|
self.assertEqual(field.hint, "New hint")
|
|
|
|
self.assertEqual(field.required, True)
|
2024-08-15 16:38:12 +02:00
|
|
|
self.assertEqual(field.editable_by_user, False)
|
2024-03-30 13:12:18 +01:00
|
|
|
|
2020-09-30 22:49:33 +02:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
2024-05-22 12:12:20 +02:00
|
|
|
info={"name": "Name "},
|
2020-09-30 22:49:33 +02:00
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
field.refresh_from_db()
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(field.name, "Name")
|
2024-05-22 12:12:20 +02:00
|
|
|
|
|
|
|
# Empty string for hint should set it to an empty string
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={"hint": ""},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
field.refresh_from_db()
|
|
|
|
self.assertEqual(field.hint, "")
|
|
|
|
|
|
|
|
def test_update_display_in_profile_summary(self) -> None:
|
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
|
|
|
|
field = CustomProfileField.objects.get(name="Phone number", realm=realm)
|
|
|
|
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"display_in_profile_summary": "invalid value",
|
|
|
|
},
|
|
|
|
)
|
2024-06-19 18:02:21 +02:00
|
|
|
msg = "display_in_profile_summary is not valid JSON"
|
2024-05-22 12:12:20 +02:00
|
|
|
self.assert_json_error(result, msg)
|
|
|
|
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"display_in_profile_summary": "true",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
field.refresh_from_db()
|
2022-07-12 21:04:47 +02:00
|
|
|
self.assertEqual(field.display_in_profile_summary, True)
|
2020-09-30 22:49:33 +02:00
|
|
|
|
2024-05-22 11:06:24 +02:00
|
|
|
# Not sending display_in_profile_summary should not set it to false.
|
2018-04-08 09:50:05 +02:00
|
|
|
result = self.client_patch(
|
2020-06-09 00:25:09 +02:00
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
2022-07-12 21:04:47 +02:00
|
|
|
info={
|
2024-05-22 11:06:24 +02:00
|
|
|
"hint": "New hint",
|
2022-07-12 21:04:47 +02:00
|
|
|
},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2024-05-22 11:06:24 +02:00
|
|
|
field.refresh_from_db()
|
|
|
|
self.assertEqual(field.hint, "New hint")
|
|
|
|
self.assertEqual(field.display_in_profile_summary, True)
|
2018-04-08 09:50:05 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2024-05-22 11:06:24 +02:00
|
|
|
# Setting display_in_profile_summary to True for the 2nd field. This
|
|
|
|
# will be useful in the next test where we will test that maximum 2
|
|
|
|
# fields can be displayed in the profile summary.
|
|
|
|
field = CustomProfileField.objects.get(name="Pronouns", realm=realm)
|
2024-03-30 12:40:57 +01:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
2024-05-22 11:06:24 +02:00
|
|
|
"display_in_profile_summary": "true",
|
2024-03-30 12:40:57 +01:00
|
|
|
},
|
|
|
|
)
|
2024-05-22 11:06:24 +02:00
|
|
|
self.assert_json_success(result)
|
2024-03-30 12:40:57 +01:00
|
|
|
field.refresh_from_db()
|
|
|
|
self.assertEqual(field.display_in_profile_summary, True)
|
|
|
|
|
2022-07-12 21:04:47 +02:00
|
|
|
field = CustomProfileField.objects.get(name="Birthday", realm=realm)
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"display_in_profile_summary": "true",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_error(
|
|
|
|
result, "Only 2 custom profile fields can be displayed in the profile summary."
|
|
|
|
)
|
|
|
|
|
2024-05-22 11:06:24 +02:00
|
|
|
def test_update_field_data(self) -> None:
|
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2024-01-20 07:18:26 +01:00
|
|
|
field = CustomProfileField.objects.get(name="Favorite editor", realm=realm)
|
2024-05-22 11:06:24 +02:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={"field_data": "invalid"},
|
|
|
|
)
|
2024-06-19 18:02:21 +02:00
|
|
|
self.assert_json_error(result, "field_data is not valid JSON")
|
2024-01-20 07:18:26 +01:00
|
|
|
|
2024-05-22 11:06:24 +02:00
|
|
|
field_data = orjson.dumps(
|
|
|
|
{
|
|
|
|
"0": "Vim",
|
|
|
|
"1": {"order": "2", "text": "Emacs"},
|
|
|
|
}
|
|
|
|
).decode()
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={"field_data": field_data},
|
|
|
|
)
|
|
|
|
self.assert_json_error(result, "field_data is not a dict")
|
|
|
|
|
|
|
|
field_data = orjson.dumps(
|
|
|
|
{
|
|
|
|
"0": {"order": "1", "text": "Vim"},
|
|
|
|
"1": {"order": "2", "text": "Emacs"},
|
|
|
|
"2": {"order": "3", "text": "Notepad"},
|
|
|
|
}
|
|
|
|
).decode()
|
2024-01-20 07:18:26 +01:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
2024-05-22 11:06:24 +02:00
|
|
|
"field_data": field_data,
|
2024-01-20 07:18:26 +01:00
|
|
|
},
|
|
|
|
)
|
2024-05-22 11:06:24 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
# We need this test to add coverage for the case where field_data
|
|
|
|
# is loaded from existing field info for validation.
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"hint": "new hint",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
field.refresh_from_db()
|
|
|
|
self.assertEqual(field.hint, "new hint")
|
2024-01-20 07:18:26 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_update_is_aware_of_uniqueness(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2021-02-12 08:19:30 +01:00
|
|
|
field_1 = try_add_realm_custom_profile_field(realm, "Phone", CustomProfileField.SHORT_TEXT)
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
field_2 = try_add_realm_custom_profile_field(
|
|
|
|
realm, "Phone 1", CustomProfileField.SHORT_TEXT
|
|
|
|
)
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-05-06 11:35:34 +02:00
|
|
|
self.assertTrue(self.custom_field_exists_in_realm(field_1.id))
|
|
|
|
self.assertTrue(self.custom_field_exists_in_realm(field_2.id))
|
2017-03-17 10:07:22 +01:00
|
|
|
result = self.client_patch(
|
2020-06-09 00:25:09 +02:00
|
|
|
f"/json/realm/profile_fields/{field_2.id}",
|
2022-04-07 19:00:21 +02:00
|
|
|
info={"name": "Phone"},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "A field with that label already exists.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
def assert_error_update_invalid_value(
|
|
|
|
self, field_name: str, new_value: object, error_msg: str
|
|
|
|
) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2018-04-29 11:55:16 +02:00
|
|
|
field = CustomProfileField.objects.get(name=field_name, realm=realm)
|
|
|
|
|
|
|
|
# Update value of field
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
|
|
|
"/json/users/me/profile_data",
|
2021-02-12 08:20:45 +01:00
|
|
|
{"data": orjson.dumps([{"id": field.id, "value": new_value}]).decode()},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-04-29 11:55:16 +02:00
|
|
|
self.assert_json_error(result, error_msg)
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-04-29 11:55:16 +02:00
|
|
|
def test_update_invalid_field(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
data = [{"id": 1234, "value": "12"}]
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
|
|
|
"/json/users/me/profile_data",
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"data": orjson.dumps(data).decode(),
|
2021-02-12 08:19:30 +01:00
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_error(result, "Field id 1234 not found.")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_update_invalid_short_text(self) -> None:
|
2018-04-29 11:55:16 +02:00
|
|
|
field_name = "Phone number"
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assert_error_update_invalid_value(
|
2021-02-12 08:20:45 +01:00
|
|
|
field_name, "t" * 201, f"{field_name} is too long (limit: 50 characters)"
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-04-03 18:06:13 +02:00
|
|
|
def test_update_invalid_date(self) -> None:
|
2018-04-29 11:55:16 +02:00
|
|
|
field_name = "Birthday"
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assert_error_update_invalid_value(field_name, "a-b-c", f"{field_name} is not a date")
|
|
|
|
self.assert_error_update_invalid_value(
|
|
|
|
field_name, "1909-3-5", f"{field_name} is not a date"
|
|
|
|
)
|
2021-09-21 16:52:15 +02:00
|
|
|
self.assert_error_update_invalid_value(field_name, [123], f"{field_name} is not a string")
|
2018-04-03 18:06:13 +02:00
|
|
|
|
2018-04-25 19:20:58 +02:00
|
|
|
def test_update_invalid_url(self) -> None:
|
2019-06-28 09:25:26 +02:00
|
|
|
field_name = "Favorite website"
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assert_error_update_invalid_value(field_name, "not URL", f"{field_name} is not a URL")
|
2018-04-25 19:20:58 +02:00
|
|
|
|
2018-05-06 09:43:38 +02:00
|
|
|
def test_update_invalid_user_field(self) -> None:
|
|
|
|
field_name = "Mentor"
|
|
|
|
invalid_user_id = 1000
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assert_error_update_invalid_value(
|
2024-07-13 06:16:55 +02:00
|
|
|
field_name, [invalid_user_id], f"Invalid user IDs: {invalid_user_id}"
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-05-06 09:43:38 +02:00
|
|
|
|
2018-04-29 11:55:16 +02:00
|
|
|
def test_update_profile_data_successfully(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2024-07-12 02:30:23 +02:00
|
|
|
fields: list[tuple[str, str | list[int]]] = [
|
2021-02-12 08:20:45 +01:00
|
|
|
("Phone number", "*short* text data"),
|
|
|
|
("Biography", "~~short~~ **long** text data"),
|
|
|
|
("Favorite food", "long short text data"),
|
2022-06-30 19:19:21 +02:00
|
|
|
("Favorite editor", "0"),
|
2021-02-12 08:20:45 +01:00
|
|
|
("Birthday", "1909-03-05"),
|
|
|
|
("Favorite website", "https://zulip.com"),
|
|
|
|
("Mentor", [self.example_user("cordelia").id]),
|
2022-09-12 20:05:05 +02:00
|
|
|
("GitHub username", "zulip-mobile"),
|
2022-10-01 12:16:11 +02:00
|
|
|
("Pronouns", "he/him"),
|
2017-03-17 10:07:22 +01:00
|
|
|
]
|
|
|
|
|
2024-07-12 02:30:17 +02:00
|
|
|
data: list[ProfileDataElementUpdateDict] = []
|
|
|
|
expected_value: dict[int, ProfileDataElementValue] = {}
|
2024-07-12 02:30:23 +02:00
|
|
|
expected_rendered_value: dict[int, str | None] = {}
|
2024-07-14 21:47:33 +02:00
|
|
|
for name, value in fields:
|
2018-03-19 20:17:52 +01:00
|
|
|
field = CustomProfileField.objects.get(name=name, realm=realm)
|
2021-02-12 08:19:30 +01:00
|
|
|
data.append(
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"id": field.id,
|
|
|
|
"value": value,
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
)
|
2022-07-08 16:53:36 +02:00
|
|
|
expected_value[field.id] = value
|
|
|
|
expected_rendered_value[field.id] = (
|
|
|
|
markdown_convert(value).rendered_content
|
|
|
|
if field.is_renderable() and isinstance(value, str)
|
|
|
|
else None
|
|
|
|
)
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-03-19 20:17:52 +01:00
|
|
|
# Update value of field
|
2020-06-21 03:22:21 +02:00
|
|
|
result = self.client_patch(
|
|
|
|
"/json/users/me/profile_data",
|
2020-08-07 01:09:47 +02:00
|
|
|
{"data": orjson.dumps([{"id": f["id"], "value": f["value"]} for f in data]).decode()},
|
2020-06-21 03:22:21 +02:00
|
|
|
)
|
2017-03-17 10:07:22 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
iago = self.example_user("iago")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2021-09-18 15:02:13 +02:00
|
|
|
for field_dict in iago.profile_data():
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(field_dict["value"], expected_value[field_dict["id"]])
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertEqual(
|
2021-02-12 08:20:45 +01:00
|
|
|
field_dict["rendered_value"], expected_rendered_value[field_dict["id"]]
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
for k in ["id", "type", "name", "field_data"]:
|
2017-05-10 23:37:20 +02:00
|
|
|
self.assertIn(k, field_dict)
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-03-19 20:17:52 +01:00
|
|
|
# Update value of one field.
|
2021-02-12 08:20:45 +01:00
|
|
|
field = CustomProfileField.objects.get(name="Biography", realm=realm)
|
2021-02-12 08:19:30 +01:00
|
|
|
data = [
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"id": field.id,
|
|
|
|
"value": "foobar",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
]
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/json/users/me/profile_data", {"data": orjson.dumps(data).decode()}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-03-17 10:07:22 +01:00
|
|
|
self.assert_json_success(result)
|
2021-09-18 15:02:13 +02:00
|
|
|
for field_dict in iago.profile_data():
|
2021-02-12 08:20:45 +01:00
|
|
|
if field_dict["id"] == field.id:
|
|
|
|
self.assertEqual(field_dict["value"], "foobar")
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2021-03-24 20:57:55 +01:00
|
|
|
def test_update_invalid_select_field(self) -> None:
|
2018-04-29 11:55:16 +02:00
|
|
|
field_name = "Favorite editor"
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assert_error_update_invalid_value(
|
|
|
|
field_name, "foobar", f"'foobar' is not a valid choice for '{field_name}'."
|
|
|
|
)
|
2018-04-29 11:55:16 +02:00
|
|
|
|
2021-03-24 20:57:55 +01:00
|
|
|
def test_update_select_field_successfully(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
field = CustomProfileField.objects.get(name="Favorite editor", realm=realm)
|
2021-02-12 08:19:30 +01:00
|
|
|
data = [
|
|
|
|
{
|
2021-02-12 08:20:45 +01:00
|
|
|
"id": field.id,
|
2022-06-30 19:19:21 +02:00
|
|
|
"value": "1",
|
2021-02-12 08:19:30 +01:00
|
|
|
}
|
|
|
|
]
|
2018-04-08 09:50:05 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/json/users/me/profile_data", {"data": orjson.dumps(data).decode()}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-04-08 09:50:05 +02:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2018-11-06 10:05:31 +01:00
|
|
|
def test_null_value_and_rendered_value(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
2018-11-06 10:05:31 +01:00
|
|
|
realm = get_realm("zulip")
|
|
|
|
|
|
|
|
quote = try_add_realm_custom_profile_field(
|
|
|
|
realm=realm,
|
|
|
|
name="Quote",
|
|
|
|
hint="Saying or phrase which you known for.",
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
field_type=CustomProfileField.SHORT_TEXT,
|
2018-11-06 10:05:31 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
iago = self.example_user("iago")
|
2021-09-18 15:02:13 +02:00
|
|
|
iago_profile_quote = iago.profile_data()[-1]
|
2018-11-06 10:05:31 +01:00
|
|
|
value = iago_profile_quote["value"]
|
|
|
|
rendered_value = iago_profile_quote["rendered_value"]
|
|
|
|
self.assertIsNone(value)
|
|
|
|
self.assertIsNone(rendered_value)
|
|
|
|
|
2022-07-08 17:17:46 +02:00
|
|
|
update_dict: ProfileDataElementUpdateDict = {
|
2018-11-06 10:05:31 +01:00
|
|
|
"id": quote.id,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
"value": "***beware*** of jealousy...",
|
2018-11-06 10:05:31 +01:00
|
|
|
}
|
2019-10-01 04:22:50 +02:00
|
|
|
do_update_user_custom_profile_data_if_changed(iago, [update_dict])
|
2018-11-06 10:05:31 +01:00
|
|
|
|
2021-09-18 15:02:13 +02:00
|
|
|
iago_profile_quote = self.example_user("iago").profile_data()[-1]
|
2018-11-06 10:05:31 +01:00
|
|
|
value = iago_profile_quote["value"]
|
|
|
|
rendered_value = iago_profile_quote["rendered_value"]
|
|
|
|
self.assertIsNotNone(value)
|
|
|
|
self.assertIsNotNone(rendered_value)
|
|
|
|
self.assertEqual("<p><strong><em>beware</em></strong> of jealousy...</p>", rendered_value)
|
2019-08-24 15:10:49 +02:00
|
|
|
|
2019-10-01 04:13:32 +02:00
|
|
|
def test_do_update_value_not_changed(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(iago)
|
2019-10-01 04:13:32 +02:00
|
|
|
realm = get_realm("zulip")
|
|
|
|
|
|
|
|
# Set field value:
|
|
|
|
field = CustomProfileField.objects.get(name="Mentor", realm=realm)
|
2024-07-12 02:30:17 +02:00
|
|
|
data: list[ProfileDataElementUpdateDict] = [
|
2021-02-12 08:20:45 +01:00
|
|
|
{"id": field.id, "value": [self.example_user("aaron").id]},
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
]
|
2019-10-01 04:22:50 +02:00
|
|
|
do_update_user_custom_profile_data_if_changed(iago, data)
|
2019-10-01 04:13:32 +02:00
|
|
|
|
2022-04-14 23:46:56 +02:00
|
|
|
with mock.patch(
|
|
|
|
"zerver.actions.custom_profile_fields.notify_user_update_custom_profile_data"
|
|
|
|
) as mock_notify:
|
2019-10-01 04:13:32 +02:00
|
|
|
# Attempting to "update" the field value, when it wouldn't actually change,
|
2021-05-28 21:49:38 +02:00
|
|
|
# shouldn't trigger notify.
|
2019-10-01 04:22:50 +02:00
|
|
|
do_update_user_custom_profile_data_if_changed(iago, data)
|
2019-10-01 04:13:32 +02:00
|
|
|
mock_notify.assert_not_called()
|
|
|
|
|
2022-06-14 16:33:14 +02:00
|
|
|
def test_removing_option_from_select_field(self) -> None:
|
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
field = CustomProfileField.objects.get(name="Favorite editor", realm=realm)
|
|
|
|
self.assertTrue(
|
2022-06-30 19:19:21 +02:00
|
|
|
CustomProfileFieldValue.objects.filter(field_id=field.id, value="0").exists()
|
2022-06-14 16:33:14 +02:00
|
|
|
)
|
|
|
|
self.assertTrue(
|
2022-06-30 19:19:21 +02:00
|
|
|
CustomProfileFieldValue.objects.filter(field_id=field.id, value="1").exists()
|
2022-06-14 16:33:14 +02:00
|
|
|
)
|
|
|
|
|
2022-06-30 19:19:21 +02:00
|
|
|
new_options = {"1": {"text": "Emacs", "order": "1"}}
|
2022-06-14 16:33:14 +02:00
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={"name": "Favorite editor", "field_data": orjson.dumps(new_options).decode()},
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.assertFalse(
|
2022-06-30 19:19:21 +02:00
|
|
|
CustomProfileFieldValue.objects.filter(field_id=field.id, value="0").exists()
|
2022-06-14 16:33:14 +02:00
|
|
|
)
|
|
|
|
self.assertTrue(
|
2022-06-30 19:19:21 +02:00
|
|
|
CustomProfileFieldValue.objects.filter(field_id=field.id, value="1").exists()
|
2022-06-14 16:33:14 +02:00
|
|
|
)
|
|
|
|
|
2022-09-12 20:11:56 +02:00
|
|
|
def test_default_external_account_type_field(self) -> None:
|
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
field_data = orjson.dumps(
|
|
|
|
{
|
|
|
|
"subtype": "twitter",
|
|
|
|
}
|
|
|
|
).decode()
|
|
|
|
|
|
|
|
field = CustomProfileField.objects.get(name="GitHub username", realm=realm)
|
|
|
|
# Attempting to change subtype here.
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"name": "GitHub username",
|
|
|
|
"field_type": CustomProfileField.EXTERNAL_ACCOUNT,
|
|
|
|
"field_data": field_data,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_error(result, "Default custom field cannot be updated.")
|
|
|
|
|
|
|
|
field_data = orjson.dumps(
|
|
|
|
{
|
|
|
|
"subtype": "github",
|
|
|
|
}
|
|
|
|
).decode()
|
|
|
|
|
|
|
|
# Attempting to change name here.
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"name": "GitHub",
|
|
|
|
"field_type": CustomProfileField.EXTERNAL_ACCOUNT,
|
|
|
|
"field_data": field_data,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_error(result, "Default custom field cannot be updated.")
|
|
|
|
|
|
|
|
field_data = orjson.dumps(
|
|
|
|
{
|
|
|
|
"subtype": "github",
|
|
|
|
"url_pattern": "invalid",
|
|
|
|
}
|
|
|
|
).decode()
|
|
|
|
|
|
|
|
# Verify cannot change URL pattern
|
|
|
|
result = self.client_patch(
|
|
|
|
f"/json/realm/profile_fields/{field.id}",
|
|
|
|
info={
|
|
|
|
"name": "GitHub username",
|
|
|
|
"field_type": CustomProfileField.EXTERNAL_ACCOUNT,
|
|
|
|
"field_data": field_data,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assert_json_error(result, "Default custom field cannot be updated.")
|
|
|
|
|
2024-08-15 16:38:12 +02:00
|
|
|
def assert_profile_field_value(
|
|
|
|
self, user: UserProfile, field_id: int, field_value: str | None
|
|
|
|
) -> None:
|
|
|
|
for field_dict in user.profile_data():
|
|
|
|
if field_dict["id"] == field_id:
|
|
|
|
self.assertEqual(field_dict["value"], field_value)
|
|
|
|
|
|
|
|
def test_update_with_editable_by_user(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
aaron = self.example_user("aaron")
|
|
|
|
self.login("aaron")
|
|
|
|
|
|
|
|
# Create field with editable_by_user = false
|
|
|
|
realm_profile_field_data: dict[str, Any] = {}
|
|
|
|
realm_profile_field_data["name"] = "Dummy field"
|
|
|
|
realm_profile_field_data["field_type"] = CustomProfileField.SHORT_TEXT
|
|
|
|
realm_profile_field_data["editable_by_user"] = "false"
|
|
|
|
result = self.api_post(iago, "/api/v1/realm/profile_fields", info=realm_profile_field_data)
|
|
|
|
result_json = self.assert_json_success(result)
|
|
|
|
restricted_field_id = result_json["id"]
|
|
|
|
|
|
|
|
field_data = [
|
|
|
|
{
|
|
|
|
"id": restricted_field_id,
|
|
|
|
"value": "test",
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
# Admins can always change their own fields
|
|
|
|
self.assert_profile_field_value(iago, restricted_field_id, None)
|
|
|
|
result = self.api_patch(
|
|
|
|
iago, "/api/v1/users/me/profile_data", {"data": orjson.dumps(field_data).decode()}
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.assert_profile_field_value(iago, restricted_field_id, "test")
|
|
|
|
|
|
|
|
# Admins can always change fields of others
|
|
|
|
self.assert_profile_field_value(aaron, restricted_field_id, None)
|
|
|
|
result = self.api_patch(
|
|
|
|
iago, f"/api/v1/users/{aaron.id}", {"profile_data": orjson.dumps(field_data).decode()}
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.assert_profile_field_value(aaron, restricted_field_id, "test")
|
|
|
|
|
|
|
|
# Users cannot update field value when editable_by_user is false.
|
|
|
|
self.assert_profile_field_value(aaron, restricted_field_id, "test")
|
|
|
|
result = self.client_patch(
|
|
|
|
"/json/users/me/profile_data", {"data": orjson.dumps(field_data).decode()}
|
|
|
|
)
|
|
|
|
self.assert_json_error(
|
|
|
|
result,
|
|
|
|
"You are not allowed to change this field. Contact an administrator to update it.",
|
|
|
|
)
|
|
|
|
self.assert_profile_field_value(aaron, restricted_field_id, "test")
|
|
|
|
|
|
|
|
# Change editable_by_user to true.
|
|
|
|
data = {}
|
|
|
|
data["editable_by_user"] = "true"
|
|
|
|
result = self.api_patch(
|
|
|
|
iago, f"/api/v1/realm/profile_fields/{restricted_field_id}", info=data
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
# Users can update field value when editable_by_user is true
|
|
|
|
self.assert_profile_field_value(aaron, restricted_field_id, "test")
|
|
|
|
field_data[0]["value"] = "test2"
|
|
|
|
result = self.client_patch(
|
|
|
|
"/json/users/me/profile_data", {"data": orjson.dumps(field_data).decode()}
|
|
|
|
)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.assert_profile_field_value(aaron, restricted_field_id, "test2")
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
|
|
|
|
def test_list(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
2019-08-24 15:10:49 +02:00
|
|
|
result = self.client_get("/json/realm/profile_fields")
|
2022-06-07 01:37:01 +02:00
|
|
|
content = self.assert_json_success(result)
|
2019-08-24 15:10:49 +02:00
|
|
|
self.assertEqual(200, result.status_code)
|
2021-05-17 05:41:32 +02:00
|
|
|
self.assert_length(content["custom_fields"], self.original_count)
|
2019-08-24 15:10:49 +02:00
|
|
|
|
|
|
|
def test_list_order(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2019-08-24 15:10:49 +02:00
|
|
|
order = (
|
|
|
|
CustomProfileField.objects.filter(realm=realm)
|
2021-02-12 08:20:45 +01:00
|
|
|
.order_by("-order")
|
|
|
|
.values_list("order", flat=True)
|
2019-08-24 15:10:49 +02:00
|
|
|
)
|
2022-06-23 22:00:19 +02:00
|
|
|
# Until https://github.com/typeddjango/django-stubs/issues/444 gets resolved,
|
|
|
|
# we need the cast here to ensure the value list is correctly typed.
|
|
|
|
assert all(isinstance(item, int) for item in order)
|
|
|
|
try_reorder_realm_custom_profile_fields(realm, cast(Iterable[int], order))
|
2019-08-24 15:10:49 +02:00
|
|
|
result = self.client_get("/json/realm/profile_fields")
|
2022-06-07 01:37:01 +02:00
|
|
|
content = self.assert_json_success(result)
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertListEqual(
|
|
|
|
content["custom_fields"], sorted(content["custom_fields"], key=lambda x: -x["id"])
|
|
|
|
)
|
2019-08-24 15:10:49 +02:00
|
|
|
|
2019-10-20 19:15:44 +02:00
|
|
|
def test_get_custom_profile_fields_from_api(self) -> None:
|
|
|
|
iago = self.example_user("iago")
|
|
|
|
test_bot = self.create_test_bot("foo-bot", iago)
|
2020-03-12 13:51:54 +01:00
|
|
|
self.login_user(iago)
|
2019-10-20 19:15:44 +02:00
|
|
|
|
2023-09-27 02:10:49 +02:00
|
|
|
with self.assert_database_query_count(5):
|
2021-02-12 08:19:30 +01:00
|
|
|
response = self.client_get(
|
|
|
|
"/json/users", {"client_gravatar": "false", "include_custom_profile_fields": "true"}
|
|
|
|
)
|
2020-02-09 00:26:43 +01:00
|
|
|
|
2022-06-07 01:37:01 +02:00
|
|
|
raw_users_data = self.assert_json_success(response)["members"]
|
2019-10-20 19:15:44 +02:00
|
|
|
|
|
|
|
iago_raw_data = None
|
|
|
|
test_bot_raw_data = None
|
|
|
|
|
|
|
|
for user_dict in raw_users_data:
|
|
|
|
if user_dict["user_id"] == iago.id:
|
|
|
|
iago_raw_data = user_dict
|
|
|
|
continue
|
|
|
|
if user_dict["user_id"] == test_bot.id:
|
|
|
|
test_bot_raw_data = user_dict
|
|
|
|
continue
|
|
|
|
|
|
|
|
if (not iago_raw_data) or (not test_bot_raw_data):
|
|
|
|
raise AssertionError("Could not find required data from the response.")
|
|
|
|
|
|
|
|
expected_keys_for_iago = {
|
2020-03-12 13:51:54 +01:00
|
|
|
"delivery_email",
|
2021-02-12 08:19:30 +01:00
|
|
|
"email",
|
|
|
|
"user_id",
|
|
|
|
"avatar_url",
|
|
|
|
"avatar_version",
|
|
|
|
"is_admin",
|
|
|
|
"is_guest",
|
2021-05-28 12:51:50 +02:00
|
|
|
"is_billing_admin",
|
2021-02-12 08:19:30 +01:00
|
|
|
"is_bot",
|
|
|
|
"is_owner",
|
2021-04-11 07:38:09 +02:00
|
|
|
"role",
|
2021-02-12 08:19:30 +01:00
|
|
|
"full_name",
|
|
|
|
"timezone",
|
|
|
|
"is_active",
|
|
|
|
"date_joined",
|
|
|
|
"profile_data",
|
|
|
|
}
|
2019-10-20 19:15:44 +02:00
|
|
|
self.assertEqual(set(iago_raw_data.keys()), expected_keys_for_iago)
|
|
|
|
self.assertNotEqual(iago_raw_data["profile_data"], {})
|
|
|
|
|
|
|
|
expected_keys_for_test_bot = {
|
2020-03-12 13:51:54 +01:00
|
|
|
"delivery_email",
|
2021-02-12 08:19:30 +01:00
|
|
|
"email",
|
|
|
|
"user_id",
|
|
|
|
"avatar_url",
|
|
|
|
"avatar_version",
|
|
|
|
"is_admin",
|
|
|
|
"is_guest",
|
|
|
|
"is_bot",
|
|
|
|
"is_owner",
|
2021-05-28 12:51:50 +02:00
|
|
|
"is_billing_admin",
|
2021-04-11 07:38:09 +02:00
|
|
|
"role",
|
2021-02-12 08:19:30 +01:00
|
|
|
"full_name",
|
|
|
|
"timezone",
|
|
|
|
"is_active",
|
|
|
|
"date_joined",
|
|
|
|
"bot_type",
|
|
|
|
"bot_owner_id",
|
|
|
|
}
|
2019-10-20 19:15:44 +02:00
|
|
|
self.assertEqual(set(test_bot_raw_data.keys()), expected_keys_for_test_bot)
|
|
|
|
self.assertEqual(test_bot_raw_data["bot_type"], 1)
|
|
|
|
self.assertEqual(test_bot_raw_data["bot_owner_id"], iago_raw_data["user_id"])
|
|
|
|
|
2020-09-13 00:11:30 +02:00
|
|
|
response = self.client_get("/json/users", {"client_gravatar": "false"})
|
2022-06-07 01:37:01 +02:00
|
|
|
raw_users_data = self.assert_json_success(response)["members"]
|
2019-10-20 19:15:44 +02:00
|
|
|
for user_dict in raw_users_data:
|
|
|
|
with self.assertRaises(KeyError):
|
|
|
|
user_dict["profile_data"]
|
|
|
|
|
2019-10-25 20:28:53 +02:00
|
|
|
def test_get_custom_profile_fields_from_api_for_single_user(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
2021-10-26 09:15:16 +02:00
|
|
|
do_change_user_setting(
|
|
|
|
self.example_user("iago"),
|
|
|
|
"email_address_visibility",
|
|
|
|
UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
|
|
|
|
acting_user=None,
|
|
|
|
)
|
2019-10-25 20:28:53 +02:00
|
|
|
expected_keys = {
|
2021-02-12 08:19:30 +01:00
|
|
|
"result",
|
|
|
|
"msg",
|
|
|
|
"max_message_id",
|
|
|
|
"user_id",
|
|
|
|
"avatar_url",
|
|
|
|
"full_name",
|
|
|
|
"email",
|
|
|
|
"is_bot",
|
|
|
|
"is_admin",
|
|
|
|
"is_owner",
|
2021-05-28 12:51:50 +02:00
|
|
|
"is_billing_admin",
|
2021-04-11 07:38:09 +02:00
|
|
|
"role",
|
2021-02-12 08:19:30 +01:00
|
|
|
"profile_data",
|
|
|
|
"avatar_version",
|
|
|
|
"timezone",
|
|
|
|
"delivery_email",
|
|
|
|
"is_active",
|
|
|
|
"is_guest",
|
|
|
|
"date_joined",
|
|
|
|
}
|
2019-10-25 20:28:53 +02:00
|
|
|
|
|
|
|
url = "/json/users/me"
|
|
|
|
response = self.client_get(url)
|
2022-06-07 01:37:01 +02:00
|
|
|
raw_user_data = self.assert_json_success(response)
|
2019-10-25 20:28:53 +02:00
|
|
|
self.assertEqual(set(raw_user_data.keys()), expected_keys)
|
|
|
|
|
2019-10-20 19:15:44 +02:00
|
|
|
|
2019-08-24 15:10:49 +02:00
|
|
|
class ReorderCustomProfileFieldTest(CustomProfileFieldTestCase):
|
|
|
|
def test_reorder(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2020-08-07 03:48:53 +02:00
|
|
|
order = list(
|
2019-08-24 15:10:49 +02:00
|
|
|
CustomProfileField.objects.filter(realm=realm)
|
2021-02-12 08:20:45 +01:00
|
|
|
.order_by("-order")
|
|
|
|
.values_list("order", flat=True)
|
2019-08-24 15:10:49 +02:00
|
|
|
)
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/json/realm/profile_fields", info={"order": orjson.dumps(order).decode()}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-08-24 15:10:49 +02:00
|
|
|
self.assert_json_success(result)
|
2021-02-12 08:20:45 +01:00
|
|
|
fields = CustomProfileField.objects.filter(realm=realm).order_by("order")
|
2019-08-24 15:10:49 +02:00
|
|
|
for field in fields:
|
|
|
|
self.assertEqual(field.id, order[field.order])
|
|
|
|
|
|
|
|
def test_reorder_duplicates(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
|
|
|
realm = get_realm("zulip")
|
2020-08-07 03:48:53 +02:00
|
|
|
order = list(
|
2019-08-24 15:10:49 +02:00
|
|
|
CustomProfileField.objects.filter(realm=realm)
|
2021-02-12 08:20:45 +01:00
|
|
|
.order_by("-order")
|
|
|
|
.values_list("order", flat=True)
|
2019-08-24 15:10:49 +02:00
|
|
|
)
|
|
|
|
order.append(4)
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/json/realm/profile_fields", info={"order": orjson.dumps(order).decode()}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-08-24 15:10:49 +02:00
|
|
|
self.assert_json_success(result)
|
2021-02-12 08:20:45 +01:00
|
|
|
fields = CustomProfileField.objects.filter(realm=realm).order_by("order")
|
2019-08-24 15:10:49 +02:00
|
|
|
for field in fields:
|
|
|
|
self.assertEqual(field.id, order[field.order])
|
|
|
|
|
|
|
|
def test_reorder_unauthorized(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("hamlet")
|
|
|
|
realm = get_realm("zulip")
|
2020-08-07 03:48:53 +02:00
|
|
|
order = list(
|
2019-08-24 15:10:49 +02:00
|
|
|
CustomProfileField.objects.filter(realm=realm)
|
2021-02-12 08:20:45 +01:00
|
|
|
.order_by("-order")
|
|
|
|
.values_list("order", flat=True)
|
2019-08-24 15:10:49 +02:00
|
|
|
)
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/json/realm/profile_fields", info={"order": orjson.dumps(order).decode()}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-08-24 15:10:49 +02:00
|
|
|
self.assert_json_error(result, "Must be an organization administrator")
|
|
|
|
|
|
|
|
def test_reorder_invalid(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.login("iago")
|
2019-08-24 15:10:49 +02:00
|
|
|
order = [100, 200, 300]
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/json/realm/profile_fields", info={"order": orjson.dumps(order).decode()}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Invalid order mapping.")
|
2019-08-24 15:10:49 +02:00
|
|
|
order = [1, 2]
|
2021-02-12 08:19:30 +01:00
|
|
|
result = self.client_patch(
|
2021-02-12 08:20:45 +01:00
|
|
|
"/json/realm/profile_fields", info={"order": orjson.dumps(order).decode()}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assert_json_error(result, "Invalid order mapping.")
|