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 the top-level logic for testing the cURL examples
|
|
|
|
# in Zulip's API documentation; the details are in
|
|
|
|
# zerver.openapi.curl_param_value_generators.
|
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
import html
|
2019-08-07 10:55:41 +02:00
|
|
|
import json
|
2021-06-04 13:03:05 +02:00
|
|
|
import os
|
|
|
|
import re
|
2019-08-07 10:55:41 +02:00
|
|
|
import shlex
|
|
|
|
import subprocess
|
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
import markdown
|
2021-06-04 13:03:05 +02:00
|
|
|
from django.conf import settings
|
2019-08-07 10:55:41 +02:00
|
|
|
from zulip import Client
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2019-08-07 10:55:41 +02:00
|
|
|
from zerver.models import get_realm
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.openapi import markdown_extension
|
2021-04-02 12:28:50 +02:00
|
|
|
from zerver.openapi.curl_param_value_generators import (
|
|
|
|
AUTHENTICATION_LINE,
|
|
|
|
assert_all_helper_functions_called,
|
|
|
|
)
|
2021-06-04 13:40:46 +02:00
|
|
|
from zerver.openapi.openapi import get_endpoint_from_operationid
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2019-08-07 10:55:41 +02:00
|
|
|
|
2021-04-02 12:28:50 +02:00
|
|
|
def test_generated_curl_examples_for_success(client: Client) -> None:
|
|
|
|
default_authentication_line = f"{client.email}:{client.api_key}"
|
2020-08-11 01:47:49 +02:00
|
|
|
# A limited Markdown engine that just processes the code example syntax.
|
2019-08-07 10:55:41 +02:00
|
|
|
realm = get_realm("zulip")
|
2021-02-12 08:19:30 +01:00
|
|
|
md_engine = markdown.Markdown(
|
|
|
|
extensions=[markdown_extension.makeExtension(api_url=realm.uri + "/api")]
|
|
|
|
)
|
2019-08-07 10:55:41 +02:00
|
|
|
|
2020-09-12 07:06:07 +02:00
|
|
|
# We run our curl tests in alphabetical order (except that we
|
|
|
|
# delay the deactivate-user test to the very end), since we depend
|
2020-04-21 17:29:17 +02:00
|
|
|
# on "add" tests coming before "remove" tests in some cases. We
|
|
|
|
# should try to either avoid ordering dependencies or make them
|
|
|
|
# very explicit.
|
2021-06-04 13:03:05 +02:00
|
|
|
rest_endpoints_path = os.path.join(
|
2022-12-07 19:41:17 +01:00
|
|
|
settings.DEPLOY_ROOT, "templates/zerver/api/include/rest-endpoints.md"
|
2021-06-04 13:03:05 +02:00
|
|
|
)
|
2023-01-18 04:06:51 +01:00
|
|
|
with open(rest_endpoints_path) as f:
|
|
|
|
rest_endpoints_raw = f.read()
|
2021-06-04 13:03:05 +02:00
|
|
|
ENDPOINT_REGEXP = re.compile(r"/api/\s*(.*?)\)")
|
|
|
|
endpoint_list = sorted(set(re.findall(ENDPOINT_REGEXP, rest_endpoints_raw)))
|
|
|
|
|
|
|
|
for endpoint in endpoint_list:
|
|
|
|
article_name = endpoint + ".md"
|
|
|
|
file_name = os.path.join(settings.DEPLOY_ROOT, "templates/zerver/api/", article_name)
|
2021-06-04 13:23:04 +02:00
|
|
|
curl_commands_to_test = []
|
|
|
|
|
2021-06-04 13:40:46 +02:00
|
|
|
if os.path.exists(file_name):
|
2023-01-18 04:06:51 +01:00
|
|
|
with open(file_name) as f:
|
|
|
|
for line in f:
|
|
|
|
# A typical example from the Markdown source looks like this:
|
|
|
|
# {generate_code_example(curl)|...|...}
|
|
|
|
if line.startswith("{generate_code_example(curl"):
|
|
|
|
curl_commands_to_test.append(line)
|
2021-06-04 13:40:46 +02:00
|
|
|
else:
|
|
|
|
# If the file doesn't exist, then it has been
|
|
|
|
# deleted and its page is generated by the
|
|
|
|
# template. Thus, the curl example would just
|
|
|
|
# a single one following the template's pattern.
|
2021-07-09 16:09:32 +02:00
|
|
|
endpoint_path, endpoint_method = get_endpoint_from_operationid(endpoint)
|
2021-06-04 13:40:46 +02:00
|
|
|
endpoint_string = endpoint_path + ":" + endpoint_method
|
|
|
|
command = f"{{generate_code_example(curl)|{endpoint_string}|example}}"
|
|
|
|
curl_commands_to_test.append(command)
|
2021-06-04 13:23:04 +02:00
|
|
|
|
2021-06-04 13:31:55 +02:00
|
|
|
for line in curl_commands_to_test:
|
|
|
|
# To do an end-to-end test on the documentation examples
|
|
|
|
# that will be actually shown to users, we use the
|
|
|
|
# Markdown rendering pipeline to compute the user-facing
|
|
|
|
# example, and then run that to test it.
|
2021-06-23 16:32:39 +02:00
|
|
|
|
|
|
|
# Set AUTHENTICATION_LINE to default_authentication_line.
|
|
|
|
# Set this every iteration, because deactivate_own_user
|
|
|
|
# will override this for its test.
|
|
|
|
AUTHENTICATION_LINE[0] = default_authentication_line
|
|
|
|
|
2021-06-04 13:31:55 +02:00
|
|
|
curl_command_html = md_engine.convert(line.strip())
|
|
|
|
unescaped_html = html.unescape(curl_command_html)
|
2021-06-21 19:01:40 +02:00
|
|
|
curl_regex = re.compile(r"<code>curl\n(.*?)</code>", re.DOTALL)
|
|
|
|
commands = re.findall(curl_regex, unescaped_html)
|
2021-06-23 16:32:39 +02:00
|
|
|
|
2021-06-21 19:03:35 +02:00
|
|
|
for curl_command_text in commands:
|
|
|
|
curl_command_text = curl_command_text.replace(
|
|
|
|
"BOT_EMAIL_ADDRESS:BOT_API_KEY", AUTHENTICATION_LINE[0]
|
|
|
|
)
|
2021-06-04 13:31:55 +02:00
|
|
|
|
2021-06-21 19:03:35 +02:00
|
|
|
print("Testing {} ...".format(curl_command_text.split("\n")[0]))
|
2021-06-04 13:31:55 +02:00
|
|
|
|
2021-06-21 19:03:35 +02:00
|
|
|
# Turn the text into an arguments list.
|
|
|
|
generated_curl_command = [x for x in shlex.split(curl_command_text) if x != "\n"]
|
2021-06-04 13:31:55 +02:00
|
|
|
|
2021-06-21 19:03:35 +02:00
|
|
|
response_json = None
|
|
|
|
response = None
|
|
|
|
try:
|
|
|
|
# We split this across two lines so if curl fails and
|
|
|
|
# returns non-JSON output, we'll still print it.
|
2022-01-22 07:52:54 +01:00
|
|
|
response_json = subprocess.check_output(generated_curl_command, text=True)
|
2021-06-21 19:03:35 +02:00
|
|
|
response = json.loads(response_json)
|
|
|
|
assert response["result"] == "success"
|
|
|
|
except (AssertionError, Exception):
|
|
|
|
error_template = """
|
2019-08-07 10:55:41 +02:00
|
|
|
Error verifying the success of the API documentation curl example.
|
|
|
|
|
|
|
|
File: {file_name}
|
|
|
|
Line: {line}
|
2020-10-23 02:43:28 +02:00
|
|
|
Curl command:
|
2019-08-07 10:55:41 +02:00
|
|
|
{curl_command}
|
|
|
|
Response:
|
|
|
|
{response}
|
|
|
|
|
|
|
|
This test is designed to check each generate_code_example(curl) instance in the
|
|
|
|
API documentation for success. If this fails then it means that the curl example
|
|
|
|
that was generated was faulty and when tried, it resulted in an unsuccessful
|
|
|
|
response.
|
|
|
|
|
|
|
|
Common reasons for why this could occur:
|
|
|
|
1. One or more example values in zerver/openapi/zulip.yaml for this endpoint
|
|
|
|
do not line up with the values in the test database.
|
|
|
|
2. One or more mandatory parameters were included in the "exclude" list.
|
|
|
|
|
|
|
|
To learn more about the test itself, see zerver/openapi/test_curl_examples.py.
|
|
|
|
"""
|
2021-06-21 19:03:35 +02:00
|
|
|
print(
|
|
|
|
error_template.format(
|
|
|
|
file_name=file_name,
|
|
|
|
line=line,
|
|
|
|
curl_command=generated_curl_command,
|
|
|
|
response=response_json
|
|
|
|
if response is None
|
|
|
|
else json.dumps(response, indent=4),
|
|
|
|
)
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-06-21 19:03:35 +02:00
|
|
|
raise
|
2019-10-21 12:43:00 +02:00
|
|
|
|
2021-04-02 12:28:50 +02:00
|
|
|
assert_all_helper_functions_called()
|