2020-09-15 00:24:01 +02:00
|
|
|
# Zulip's OpenAPI-based API documentation system is documented at
|
|
|
|
# https://zulip.readthedocs.io/en/latest/documentation/api.html
|
|
|
|
#
|
|
|
|
# This file contains helper functions for generating cURL examples
|
|
|
|
# based on Zulip's OpenAPI definitions, as well as test setup and
|
|
|
|
# fetching of appropriate parameter values to use when running the
|
|
|
|
# cURL examples as part of the tools/test-api test suite.
|
2019-10-21 12:43:00 +02:00
|
|
|
from functools import wraps
|
2020-06-11 00:54:34 +02:00
|
|
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
2019-10-21 12:43:00 +02:00
|
|
|
|
2019-10-23 13:09:06 +02:00
|
|
|
from django.utils.timezone import now as timezone_now
|
|
|
|
|
2022-04-14 23:53:15 +02:00
|
|
|
from zerver.actions.create_user import do_create_user
|
2022-04-14 23:44:33 +02:00
|
|
|
from zerver.actions.presence import update_user_presence
|
2022-04-14 23:54:01 +02:00
|
|
|
from zerver.actions.reactions import do_add_reaction
|
2022-04-14 23:32:56 +02:00
|
|
|
from zerver.actions.realm_linkifiers import do_add_linkifier
|
2023-07-24 21:14:42 +02:00
|
|
|
from zerver.actions.realm_playgrounds import check_add_realm_playground
|
2019-10-23 10:18:10 +02:00
|
|
|
from zerver.lib.events import do_events_register
|
2020-10-03 02:40:10 +02:00
|
|
|
from zerver.lib.initial_password import initial_password
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2023-02-28 03:46:41 +01:00
|
|
|
from zerver.lib.upload import upload_message_attachment
|
2021-04-02 12:28:50 +02:00
|
|
|
from zerver.lib.users import get_api_key
|
2024-04-18 12:23:46 +02:00
|
|
|
from zerver.models import Client, Message, NamedUserGroup, UserPresence
|
2023-12-15 02:14:24 +01:00
|
|
|
from zerver.models.realms import get_realm
|
2023-12-15 01:16:00 +01:00
|
|
|
from zerver.models.users import get_user
|
2024-02-01 06:26:58 +01:00
|
|
|
from zerver.openapi.openapi import Parameter
|
2019-10-21 12:43:00 +02:00
|
|
|
|
2020-09-02 08:14:51 +02:00
|
|
|
GENERATOR_FUNCTIONS: Dict[str, Callable[[], Dict[str, object]]] = {}
|
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
|
|
|
REGISTERED_GENERATOR_FUNCTIONS: Set[str] = set()
|
|
|
|
CALLED_GENERATOR_FUNCTIONS: Set[str] = set()
|
2021-04-02 12:28:50 +02:00
|
|
|
# This is a List rather than just a string in order to make it easier
|
|
|
|
# to write to it from another module.
|
|
|
|
AUTHENTICATION_LINE: List[str] = [""]
|
2019-10-21 12:43:00 +02:00
|
|
|
|
|
|
|
helpers = ZulipTestCase()
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-30 22:47:16 +02:00
|
|
|
def openapi_param_value_generator(
|
|
|
|
endpoints: List[str],
|
|
|
|
) -> Callable[[Callable[[], Dict[str, object]]], Callable[[], Dict[str, object]]]:
|
2023-10-09 20:41:12 +02:00
|
|
|
"""This decorator is used to register OpenAPI param value generator functions
|
2019-10-21 12:43:00 +02:00
|
|
|
with endpoints. Example usage:
|
|
|
|
|
|
|
|
@openapi_param_value_generator(["/messages/render:post"])
|
|
|
|
def ...
|
|
|
|
"""
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-06-30 22:47:16 +02:00
|
|
|
def wrapper(generator_func: Callable[[], Dict[str, object]]) -> Callable[[], Dict[str, object]]:
|
2019-10-21 12:43:00 +02:00
|
|
|
@wraps(generator_func)
|
2020-06-30 22:47:16 +02:00
|
|
|
def _record_calls_wrapper() -> Dict[str, object]:
|
2019-10-21 12:43:00 +02:00
|
|
|
CALLED_GENERATOR_FUNCTIONS.add(generator_func.__name__)
|
2020-06-30 22:47:16 +02:00
|
|
|
return generator_func()
|
2019-10-21 12:43:00 +02:00
|
|
|
|
|
|
|
REGISTERED_GENERATOR_FUNCTIONS.add(generator_func.__name__)
|
|
|
|
for endpoint in endpoints:
|
|
|
|
GENERATOR_FUNCTIONS[endpoint] = _record_calls_wrapper
|
|
|
|
|
|
|
|
return _record_calls_wrapper
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-10-21 12:43:00 +02:00
|
|
|
return wrapper
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-04-02 12:28:50 +02:00
|
|
|
def assert_all_helper_functions_called() -> None:
|
|
|
|
"""Throws an exception if any registered helpers were not called by tests"""
|
|
|
|
if REGISTERED_GENERATOR_FUNCTIONS == CALLED_GENERATOR_FUNCTIONS:
|
|
|
|
return
|
|
|
|
|
|
|
|
uncalled_functions = str(REGISTERED_GENERATOR_FUNCTIONS - CALLED_GENERATOR_FUNCTIONS)
|
|
|
|
|
|
|
|
raise Exception(f"Registered curl API generators were not called: {uncalled_functions}")
|
|
|
|
|
|
|
|
|
2020-06-30 22:47:16 +02:00
|
|
|
def patch_openapi_example_values(
|
2021-02-12 08:19:30 +01:00
|
|
|
entry: str,
|
2024-02-01 06:26:58 +01:00
|
|
|
parameters: List[Parameter],
|
2020-06-30 22:47:16 +02:00
|
|
|
request_body: Optional[Dict[str, Any]] = None,
|
2024-02-01 06:26:58 +01:00
|
|
|
) -> Tuple[List[Parameter], Optional[Dict[str, object]]]:
|
2019-11-18 15:48:49 +01:00
|
|
|
if entry not in GENERATOR_FUNCTIONS:
|
2024-02-01 06:26:58 +01:00
|
|
|
return parameters, request_body
|
2019-11-18 15:48:49 +01:00
|
|
|
func = GENERATOR_FUNCTIONS[entry]
|
2020-06-30 22:47:16 +02:00
|
|
|
realm_example_values: Dict[str, object] = func()
|
2019-11-18 15:48:49 +01:00
|
|
|
|
2024-02-01 06:26:58 +01:00
|
|
|
for parameter in parameters:
|
|
|
|
if parameter.name in realm_example_values:
|
|
|
|
parameter.example = realm_example_values[parameter.name]
|
2019-11-18 15:48:49 +01:00
|
|
|
|
2024-02-01 06:07:29 +01:00
|
|
|
if request_body is not None and "multipart/form-data" in (content := request_body["content"]):
|
|
|
|
properties = content["multipart/form-data"]["schema"]["properties"]
|
2019-11-18 15:48:49 +01:00
|
|
|
for key, property in properties.items():
|
|
|
|
if key in realm_example_values:
|
|
|
|
property["example"] = realm_example_values[key]
|
2024-02-01 06:26:58 +01:00
|
|
|
return parameters, request_body
|
2019-10-21 12:43:00 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-10-03 02:40:10 +02:00
|
|
|
@openapi_param_value_generator(["/fetch_api_key:post"])
|
|
|
|
def fetch_api_key() -> Dict[str, object]:
|
|
|
|
email = helpers.example_email("iago")
|
|
|
|
password = initial_password(email)
|
|
|
|
|
|
|
|
return {
|
|
|
|
"username": email,
|
2020-11-04 02:49:09 +01:00
|
|
|
"password": password,
|
2020-10-03 02:40:10 +02:00
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
@openapi_param_value_generator(
|
|
|
|
[
|
|
|
|
"/messages/{message_id}:get",
|
|
|
|
"/messages/{message_id}/history:get",
|
|
|
|
"/messages/{message_id}:patch",
|
|
|
|
"/messages/{message_id}:delete",
|
|
|
|
]
|
|
|
|
)
|
2020-06-30 22:47:16 +02:00
|
|
|
def iago_message_id() -> Dict[str, object]:
|
tests: Ensure stream senders get a UserMessage row.
We now complain if a test author sends a stream message
that does not result in the sender getting a
UserMessage row for the message.
This is basically 100% equivalent to complaining that
the author failed to subscribe the sender to the stream
as part of the test setup, as far as I can tell, so the
AssertionError instructs the author to subscribe the
sender to the stream.
We exempt bots from this check, although it is
plausible we should only exempt the system bots like
the notification bot.
I considered auto-subscribing the sender to the stream,
but that can be a little more expensive than the
current check, and we generally want test setup to be
explicit.
If there is some legitimate way than a subscribed human
sender can't get a UserMessage, then we probably want
an explicit test for that, or we may want to change the
backend to just write a UserMessage row in that
hypothetical situation.
For most tests, including almost all the ones fixed
here, the author just wants their test setup to
realistically reflect normal operation, and often devs
may not realize that Cordelia is not subscribed to
Denmark or not realize that Hamlet is not subscribed to
Scotland.
Some of us don't remember our Shakespeare from high
school, and our stream subscriptions don't even
necessarily reflect which countries the Bard placed his
characters in.
There may also be some legitimate use case where an
author wants to simulate sending a message to an
unsubscribed stream, but for those edge cases, they can
always set allow_unsubscribed_sender to True.
2021-12-10 13:55:48 +01:00
|
|
|
iago = helpers.example_user("iago")
|
|
|
|
helpers.subscribe(iago, "Denmark")
|
2019-10-21 12:43:00 +02:00
|
|
|
return {
|
tests: Ensure stream senders get a UserMessage row.
We now complain if a test author sends a stream message
that does not result in the sender getting a
UserMessage row for the message.
This is basically 100% equivalent to complaining that
the author failed to subscribe the sender to the stream
as part of the test setup, as far as I can tell, so the
AssertionError instructs the author to subscribe the
sender to the stream.
We exempt bots from this check, although it is
plausible we should only exempt the system bots like
the notification bot.
I considered auto-subscribing the sender to the stream,
but that can be a little more expensive than the
current check, and we generally want test setup to be
explicit.
If there is some legitimate way than a subscribed human
sender can't get a UserMessage, then we probably want
an explicit test for that, or we may want to change the
backend to just write a UserMessage row in that
hypothetical situation.
For most tests, including almost all the ones fixed
here, the author just wants their test setup to
realistically reflect normal operation, and often devs
may not realize that Cordelia is not subscribed to
Denmark or not realize that Hamlet is not subscribed to
Scotland.
Some of us don't remember our Shakespeare from high
school, and our stream subscriptions don't even
necessarily reflect which countries the Bard placed his
characters in.
There may also be some legitimate use case where an
author wants to simulate sending a message to an
unsubscribed stream, but for those edge cases, they can
always set allow_unsubscribed_sender to True.
2021-12-10 13:55:48 +01:00
|
|
|
"message_id": helpers.send_stream_message(iago, "Denmark"),
|
2019-10-21 12:43:00 +02:00
|
|
|
}
|
2019-10-21 13:00:25 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-03-07 11:20:57 +01:00
|
|
|
@openapi_param_value_generator(["/messages/{message_id}/reactions:delete"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def add_emoji_to_message() -> Dict[str, object]:
|
2021-02-12 08:20:45 +01:00
|
|
|
user_profile = helpers.example_user("iago")
|
2020-03-07 11:20:57 +01:00
|
|
|
|
2021-12-15 01:04:35 +01:00
|
|
|
# The message ID here is hardcoded based on the corresponding value
|
|
|
|
# for the example message IDs we use in zulip.yaml.
|
2023-01-29 14:02:07 +01:00
|
|
|
message_id = 47
|
2021-02-12 08:20:45 +01:00
|
|
|
emoji_name = "octopus"
|
|
|
|
emoji_code = "1f419"
|
|
|
|
reaction_type = "unicode_emoji"
|
2020-03-07 11:20:57 +01:00
|
|
|
|
2023-08-01 17:51:48 +02:00
|
|
|
message = Message.objects.select_related(*Message.DEFAULT_SELECT_RELATED).get(id=message_id)
|
2020-03-07 11:20:57 +01:00
|
|
|
do_add_reaction(user_profile, message, emoji_name, emoji_code, reaction_type)
|
|
|
|
|
|
|
|
return {}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-10-21 13:54:46 +02:00
|
|
|
@openapi_param_value_generator(["/messages/flags:post"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def update_flags_message_ids() -> Dict[str, object]:
|
2019-10-21 13:54:46 +02:00
|
|
|
stream_name = "Venice"
|
2019-11-14 06:03:17 +01:00
|
|
|
helpers.subscribe(helpers.example_user("iago"), stream_name)
|
2019-10-21 13:54:46 +02:00
|
|
|
|
2023-07-31 22:52:35 +02:00
|
|
|
messages = [
|
|
|
|
helpers.send_stream_message(helpers.example_user("iago"), stream_name) for _ in range(3)
|
|
|
|
]
|
2019-10-21 13:54:46 +02:00
|
|
|
return {
|
|
|
|
"messages": messages,
|
|
|
|
}
|
2019-10-23 09:01:17 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-10-23 09:33:43 +02:00
|
|
|
@openapi_param_value_generator(["/mark_stream_as_read:post", "/users/me/{stream_id}/topics:get"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def get_venice_stream_id() -> Dict[str, object]:
|
2019-10-23 09:01:17 +02:00
|
|
|
return {
|
|
|
|
"stream_id": helpers.get_stream_id("Venice"),
|
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-11-14 09:48:21 +01:00
|
|
|
@openapi_param_value_generator(["/streams/{stream_id}:patch"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def update_stream() -> Dict[str, object]:
|
2019-11-14 09:48:21 +01:00
|
|
|
stream = helpers.subscribe(helpers.example_user("iago"), "temp_stream 1")
|
|
|
|
return {
|
|
|
|
"stream_id": stream.id,
|
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-11-14 09:09:48 +01:00
|
|
|
@openapi_param_value_generator(["/streams/{stream_id}:delete"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def create_temp_stream_and_get_id() -> Dict[str, object]:
|
2019-11-14 09:48:21 +01:00
|
|
|
stream = helpers.subscribe(helpers.example_user("iago"), "temp_stream 2")
|
2019-11-14 09:09:48 +01:00
|
|
|
return {
|
|
|
|
"stream_id": stream.id,
|
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-10-23 09:01:17 +02:00
|
|
|
@openapi_param_value_generator(["/mark_topic_as_read:post"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def get_denmark_stream_id_and_topic() -> Dict[str, object]:
|
2019-10-23 09:01:17 +02:00
|
|
|
stream_name = "Denmark"
|
|
|
|
topic_name = "Tivoli Gardens"
|
|
|
|
|
2019-11-14 06:03:17 +01:00
|
|
|
helpers.subscribe(helpers.example_user("iago"), stream_name)
|
2020-03-07 11:43:05 +01:00
|
|
|
helpers.send_stream_message(helpers.example_user("hamlet"), stream_name, topic_name=topic_name)
|
2019-10-23 09:01:17 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
"stream_id": helpers.get_stream_id(stream_name),
|
|
|
|
"topic_name": topic_name,
|
|
|
|
}
|
2019-10-23 09:16:46 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-10-23 09:16:46 +02:00
|
|
|
@openapi_param_value_generator(["/users/me/subscriptions/properties:post"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def update_subscription_data() -> Dict[str, object]:
|
2019-11-14 06:03:17 +01:00
|
|
|
profile = helpers.example_user("iago")
|
|
|
|
helpers.subscribe(profile, "Verona")
|
|
|
|
helpers.subscribe(profile, "social")
|
2019-10-23 09:16:46 +02:00
|
|
|
return {
|
|
|
|
"subscription_data": [
|
|
|
|
{"stream_id": helpers.get_stream_id("Verona"), "property": "pin_to_top", "value": True},
|
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
|
|
|
{"stream_id": helpers.get_stream_id("social"), "property": "color", "value": "#f00f00"},
|
|
|
|
],
|
2019-10-23 09:16:46 +02:00
|
|
|
}
|
2019-10-23 10:18:10 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-11-14 06:34:39 +01:00
|
|
|
@openapi_param_value_generator(["/users/me/subscriptions:delete"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def delete_subscription_data() -> Dict[str, object]:
|
2019-11-14 06:34:39 +01:00
|
|
|
iago = helpers.example_user("iago")
|
|
|
|
zoe = helpers.example_user("ZOE")
|
|
|
|
helpers.subscribe(iago, "Verona")
|
|
|
|
helpers.subscribe(iago, "social")
|
|
|
|
helpers.subscribe(zoe, "Verona")
|
|
|
|
helpers.subscribe(zoe, "social")
|
|
|
|
return {}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-10-23 10:18:10 +02:00
|
|
|
@openapi_param_value_generator(["/events:get"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def get_events() -> Dict[str, object]:
|
2019-11-14 06:03:17 +01:00
|
|
|
profile = helpers.example_user("iago")
|
|
|
|
helpers.subscribe(profile, "Verona")
|
2019-10-23 10:18:10 +02:00
|
|
|
client = Client.objects.create(name="curl-test-client-1")
|
2022-05-03 23:54:44 +02:00
|
|
|
response = do_events_register(
|
|
|
|
profile, profile.realm, client, event_types=["message", "realm_emoji"]
|
|
|
|
)
|
2020-03-07 11:43:05 +01:00
|
|
|
helpers.send_stream_message(helpers.example_user("hamlet"), "Verona")
|
2019-10-23 10:18:10 +02:00
|
|
|
return {
|
|
|
|
"queue_id": response["queue_id"],
|
|
|
|
"last_event_id": response["last_event_id"],
|
|
|
|
}
|
2019-10-23 10:21:41 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-10-23 10:21:41 +02:00
|
|
|
@openapi_param_value_generator(["/events:delete"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def delete_event_queue() -> Dict[str, object]:
|
2019-11-14 06:03:17 +01:00
|
|
|
profile = helpers.example_user("iago")
|
2019-10-23 10:21:41 +02:00
|
|
|
client = Client.objects.create(name="curl-test-client-2")
|
2022-05-03 23:54:44 +02:00
|
|
|
response = do_events_register(profile, profile.realm, client, event_types=["message"])
|
2019-10-23 10:21:41 +02:00
|
|
|
return {
|
|
|
|
"queue_id": response["queue_id"],
|
|
|
|
"last_event_id": response["last_event_id"],
|
|
|
|
}
|
2019-10-23 13:09:06 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-01-04 19:36:00 +01:00
|
|
|
@openapi_param_value_generator(["/users/{user_id_or_email}/presence:get"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def get_user_presence() -> Dict[str, object]:
|
2019-10-23 13:09:06 +02:00
|
|
|
iago = helpers.example_user("iago")
|
|
|
|
client = Client.objects.create(name="curl-test-client-3")
|
2020-06-11 16:03:47 +02:00
|
|
|
update_user_presence(iago, client, timezone_now(), UserPresence.LEGACY_STATUS_ACTIVE_INT, False)
|
2019-10-23 13:09:06 +02:00
|
|
|
return {}
|
2019-11-14 09:16:11 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-11-18 04:57:21 +01:00
|
|
|
@openapi_param_value_generator(["/users:post"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def create_user() -> Dict[str, object]:
|
2019-11-18 04:57:21 +01:00
|
|
|
return {
|
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
|
|
|
"email": helpers.nonreg_email("test"),
|
2019-11-18 04:57:21 +01:00
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-11-14 09:16:11 +01:00
|
|
|
@openapi_param_value_generator(["/user_groups/create:post"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def create_user_group_data() -> Dict[str, object]:
|
2019-11-14 09:16:11 +01:00
|
|
|
return {
|
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
|
|
|
"members": [helpers.example_user("hamlet").id, helpers.example_user("othello").id],
|
2019-11-14 09:16:11 +01:00
|
|
|
}
|
2019-11-14 09:35:28 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
@openapi_param_value_generator(
|
|
|
|
["/user_groups/{user_group_id}:patch", "/user_groups/{user_group_id}:delete"]
|
|
|
|
)
|
2020-06-30 22:47:16 +02:00
|
|
|
def get_temp_user_group_id() -> Dict[str, object]:
|
2024-04-18 12:23:46 +02:00
|
|
|
user_group, _ = NamedUserGroup.objects.get_or_create(
|
|
|
|
name="temp",
|
|
|
|
realm=get_realm("zulip"),
|
|
|
|
can_mention_group_id=11,
|
|
|
|
realm_for_sharding=get_realm("zulip"),
|
2023-06-12 13:27:47 +02:00
|
|
|
)
|
2019-11-14 09:35:28 +01:00
|
|
|
return {
|
2020-07-16 20:39:53 +02:00
|
|
|
"user_group_id": user_group.id,
|
2019-11-14 09:35:28 +01:00
|
|
|
}
|
2019-11-18 05:47:32 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-11-18 05:47:32 +01:00
|
|
|
@openapi_param_value_generator(["/realm/filters/{filter_id}:delete"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def remove_realm_filters() -> Dict[str, object]:
|
2021-03-30 12:15:39 +02:00
|
|
|
filter_id = do_add_linkifier(
|
2022-03-14 11:50:24 +01:00
|
|
|
get_realm("zulip"),
|
|
|
|
"#(?P<id>[0-9]{2,8})",
|
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
|
|
|
"https://github.com/zulip/zulip/pull/{id}",
|
2022-03-14 11:50:24 +01:00
|
|
|
acting_user=None,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-11-18 05:47:32 +01:00
|
|
|
return {
|
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
|
|
|
"filter_id": filter_id,
|
2019-11-18 05:47:32 +01:00
|
|
|
}
|
2019-11-18 16:46:06 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-11-18 16:47:41 +01:00
|
|
|
@openapi_param_value_generator(["/realm/emoji/{emoji_name}:post", "/user_uploads:post"])
|
2020-06-30 22:47:16 +02:00
|
|
|
def upload_custom_emoji() -> Dict[str, object]:
|
2019-11-18 16:46:06 +01:00
|
|
|
return {
|
|
|
|
"filename": "zerver/tests/images/animated_img.gif",
|
|
|
|
}
|
2020-09-14 17:23:17 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-10-27 02:14:56 +01:00
|
|
|
@openapi_param_value_generator(["/realm/playgrounds:post"])
|
|
|
|
def add_realm_playground() -> Dict[str, object]:
|
|
|
|
return {
|
|
|
|
"name": "Python2 playground",
|
2021-04-17 08:26:57 +02:00
|
|
|
"pygments_language": "Python2",
|
2023-05-27 05:04:50 +02:00
|
|
|
"url_template": "https://python2.example.com?code={code}",
|
2020-10-27 02:14:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-27 02:21:22 +01:00
|
|
|
@openapi_param_value_generator(["/realm/playgrounds/{playground_id}:delete"])
|
|
|
|
def remove_realm_playground() -> Dict[str, object]:
|
2023-07-24 21:14:42 +02:00
|
|
|
playground_id = check_add_realm_playground(
|
2023-05-27 01:33:01 +02:00
|
|
|
get_realm("zulip"),
|
|
|
|
acting_user=None,
|
2020-10-27 02:21:22 +01:00
|
|
|
name="Python playground",
|
|
|
|
pygments_language="Python",
|
2023-05-27 05:04:50 +02:00
|
|
|
url_template="https://python.example.com?code={code}",
|
2020-10-27 02:21:22 +01:00
|
|
|
)
|
|
|
|
return {
|
|
|
|
"playground_id": playground_id,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-14 17:23:17 +02:00
|
|
|
@openapi_param_value_generator(["/users/{user_id}:delete"])
|
|
|
|
def deactivate_user() -> Dict[str, object]:
|
|
|
|
user_profile = do_create_user(
|
2021-02-06 14:27:06 +01:00
|
|
|
email="testuser@zulip.com",
|
|
|
|
password=None,
|
|
|
|
full_name="test_user",
|
|
|
|
realm=get_realm("zulip"),
|
|
|
|
acting_user=None,
|
2020-09-14 17:23:17 +02:00
|
|
|
)
|
2021-02-12 08:19:30 +01:00
|
|
|
return {"user_id": user_profile.id}
|
2021-04-02 12:28:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
@openapi_param_value_generator(["/users/me:delete"])
|
|
|
|
def deactivate_own_user() -> Dict[str, object]:
|
|
|
|
test_user_email = "delete-test@zulip.com"
|
|
|
|
deactivate_test_user = do_create_user(
|
|
|
|
test_user_email,
|
|
|
|
"secret",
|
|
|
|
get_realm("zulip"),
|
|
|
|
"Mr. Delete",
|
|
|
|
role=200,
|
|
|
|
acting_user=None,
|
|
|
|
)
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
test_user = get_user(test_user_email, realm)
|
|
|
|
test_user_api_key = get_api_key(test_user)
|
|
|
|
# change authentication line to allow test_client to delete itself.
|
|
|
|
AUTHENTICATION_LINE[0] = f"{deactivate_test_user.email}:{test_user_api_key}"
|
|
|
|
return {}
|
2021-07-30 11:58:11 +02:00
|
|
|
|
|
|
|
|
|
|
|
@openapi_param_value_generator(["/attachments/{attachment_id}:delete"])
|
|
|
|
def remove_attachment() -> Dict[str, object]:
|
|
|
|
user_profile = helpers.example_user("iago")
|
2023-02-28 03:46:41 +01:00
|
|
|
url = upload_message_attachment(
|
|
|
|
"dummy.txt", len(b"zulip!"), "text/plain", b"zulip!", user_profile
|
|
|
|
)
|
2021-07-30 11:58:11 +02:00
|
|
|
attachment_id = url.replace("/user_uploads/", "").split("/")[0]
|
|
|
|
|
|
|
|
return {"attachment_id": attachment_id}
|