mirror of https://github.com/zulip/zulip.git
api_docs: Detect missing arguments in curl examples.
This commit adds automated tests that make sure that every curl example command in our API docs has the '-X (POST|GET)' argument. Fixes: #11927
This commit is contained in:
parent
8339c21637
commit
cecea75457
|
@ -15,7 +15,7 @@ appear in messages and topics.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/realm/filters \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "pattern=#(?P<id>[0-9]+)" \
|
||||
|
|
|
@ -52,8 +52,8 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/users/me/subscriptions \
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/users/me/subscriptions \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d 'subscriptions=[{"name": "Verona"}]'
|
||||
```
|
||||
|
@ -61,8 +61,8 @@ curl {{ api_url }}/v1/users/me/subscriptions \
|
|||
To subscribe another user to a stream, you may pass in
|
||||
the `principals` argument, like so:
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/users/me/subscriptions \
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/users/me/subscriptions \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d 'subscriptions=[{"name": "Verona"}]' \
|
||||
-d 'principals=["ZOE@zulip.com"]'
|
||||
|
|
|
@ -38,8 +38,8 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/users \
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/users \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "email=newbie@zulip.com" \
|
||||
-d "full_name=New User" \
|
||||
|
|
|
@ -19,7 +19,7 @@ the Zulip Help Center.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X DELETE {{ api_url }}/v1/messages/{message_id} \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
```
|
||||
|
|
|
@ -41,7 +41,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X "DELETE" {{ api_url }}/v1/events \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
-d 'queue_id=1515096410:1'
|
||||
|
|
|
@ -17,8 +17,8 @@ in a Zulip production server.
|
|||
{start_tabs}
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/dev_fetch_api_key \
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/dev_fetch_api_key \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "username=iago@zulip.com"
|
||||
```
|
||||
|
|
|
@ -31,14 +31,14 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/streams -u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
||||
You may pass in one or more of the parameters mentioned above
|
||||
as URL query parameters, like so:
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/streams?include_public=false \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -33,13 +33,13 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/users -u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
||||
You may pass the `client_gravatar` query parameter as follows:
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/users?client_gravatar=true \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -62,8 +62,8 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl -G {{ api_url }}/v1/events \
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/events \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
-d "queue_id=1375801870:2942" \
|
||||
-d "last_event_id=-1"
|
||||
|
|
|
@ -18,7 +18,7 @@ Note that edit history may be disabled in some organizations; see the
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/messages/<message_id>/history \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -63,7 +63,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/messages \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "anchor=42" \
|
||||
|
|
|
@ -29,7 +29,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/realm/emoji \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -23,7 +23,7 @@ for details on the data model for presence in Zulip.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/users/<email>/presence \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -31,7 +31,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/users/me \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ UI).
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/messages/<msg_id> \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
```
|
||||
|
|
|
@ -30,8 +30,8 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl X GET {{ api_url }}/v1/get_stream_id?stream=Denmark \
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/get_stream_id?stream=Denmark \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/users/me/<stream_id>/topics \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -32,7 +32,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/users/me/subscriptions \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -15,7 +15,7 @@ Fetches all of the user groups in the organization.
|
|||
|
||||
<div data-language="curl" markdown="1">
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/user_groups \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -16,8 +16,8 @@ in messages and topics.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/realm/filters \
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/realm/filters \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
```
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ Marks all of the current user's unread messages as read.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/mark_all_as_read \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
@ -48,7 +48,7 @@ Mark all the unread messages in a stream as read.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/mark_stream_as_read \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "stream_id=42"
|
||||
|
@ -84,7 +84,7 @@ Mark all the unread messages in a topic as read.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/mark_topic_as_read \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "stream_id=42" \
|
||||
|
|
|
@ -15,7 +15,7 @@ UI, and are not included in the user's unread count totals.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X PATCH {{ api_url }}/v1/users/me/subscriptions/muted_topics \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "stream=Verona"
|
||||
|
|
|
@ -75,8 +75,8 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/register \
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/register \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
-d 'event_types=["message"]'
|
||||
```
|
||||
|
|
|
@ -15,7 +15,7 @@ in messages and topics.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X DELETE {{ api_url }}/v1/realm/filters/<filter_id> \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
|
|
@ -40,7 +40,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X "DELETE" {{ api_url }}/v1/users/me/subscriptions \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d 'subscriptions=["Denmark"]'
|
||||
|
@ -48,7 +48,7 @@ curl -X "DELETE" {{ api_url }}/v1/users/me/subscriptions \
|
|||
|
||||
You may specify the `principals` argument like so:
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X "DELETE" {{ api_url }}/v1/users/me/subscriptions \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d 'subscriptions=["Denmark"]' \
|
||||
|
|
|
@ -34,8 +34,8 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/messages/render \
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/messages/render \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d $"content=**foo**"
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
# For stream messages
|
||||
curl -X POST {{ api_url }}/v1/messages \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
|
|
|
@ -21,8 +21,8 @@ Fetch global settings for a Zulip server.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/server_settings \
|
||||
``` curl
|
||||
curl -X GET {{ api_url }}/v1/server_settings \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY
|
||||
```
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/typing \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "op=start" \
|
||||
|
|
|
@ -44,7 +44,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/messages/flags \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d "messages=[4,8,15]" \
|
||||
|
|
|
@ -38,7 +38,7 @@ zulip(config).then((client) => {
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X "PATCH" {{ api_url }}/v1/messages/<msg_id> \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d $"content=New content"
|
||||
|
|
|
@ -15,7 +15,7 @@ per-stream notification settings.
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/users/me/subscriptions/properties \
|
||||
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
|
||||
-d 'subscription_data=[{"stream_id": 1, \
|
||||
|
|
|
@ -16,8 +16,8 @@ organization. Access to this endpoint depends on the
|
|||
|
||||
{tab|curl}
|
||||
|
||||
```
|
||||
curl {{ api_url }}/v1/realm/emoji/<emoji_name> \
|
||||
``` curl
|
||||
curl -X POST {{ api_url }}/v1/realm/emoji/<emoji_name> \
|
||||
-F "data=@/path/to/img.png" \
|
||||
-u USER_EMAIL:API_KEY
|
||||
```
|
||||
|
|
|
@ -80,8 +80,9 @@ import re
|
|||
import markdown
|
||||
from django.utils.html import escape
|
||||
from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension
|
||||
from zerver.lib.exceptions import BugdownRenderingException
|
||||
from zerver.lib.tex import render_tex
|
||||
from typing import Any, Dict, Iterable, List, MutableSequence
|
||||
from typing import Any, Dict, Iterable, List, MutableSequence, Optional
|
||||
|
||||
# Global vars
|
||||
FENCE_RE = re.compile("""
|
||||
|
@ -107,12 +108,44 @@ FENCE_RE = re.compile("""
|
|||
CODE_WRAP = '<pre><code%s>%s\n</code></pre>'
|
||||
LANG_TAG = ' class="%s"'
|
||||
|
||||
def validate_curl_content(lines: List[str]) -> None:
|
||||
error_msg = """
|
||||
Missing required -X argument in curl command:
|
||||
|
||||
{command}
|
||||
""".strip()
|
||||
|
||||
for line in lines:
|
||||
regex = r'curl [-]X "?(GET|DELETE|PATCH|POST)"?'
|
||||
if line.startswith('curl'):
|
||||
if re.search(regex, line) is None:
|
||||
raise BugdownRenderingException(error_msg.format(command=line.strip()))
|
||||
|
||||
|
||||
CODE_VALIDATORS = {
|
||||
'curl': validate_curl_content,
|
||||
}
|
||||
|
||||
class FencedCodeExtension(markdown.Extension):
|
||||
def __init__(self, config: Optional[Dict[str, Any]]=None) -> None:
|
||||
if config is None:
|
||||
config = {}
|
||||
self.config = {
|
||||
'run_content_validators': [
|
||||
config.get('run_content_validators', False),
|
||||
'Boolean specifying whether to run content validation code in CodeHandler'
|
||||
]
|
||||
}
|
||||
|
||||
for key, value in config.items():
|
||||
self.setConfig(key, value)
|
||||
|
||||
def extendMarkdown(self, md: markdown.Markdown, md_globals: Dict[str, Any]) -> None:
|
||||
""" Add FencedBlockPreprocessor to the Markdown instance. """
|
||||
md.registerExtension(self)
|
||||
md.preprocessors.register(FencedBlockPreprocessor(md), 'fenced_code_block', 25)
|
||||
processor = FencedBlockPreprocessor(
|
||||
md, run_content_validators=self.config['run_content_validators'][0])
|
||||
md.preprocessors.register(processor, 'fenced_code_block', 25)
|
||||
|
||||
|
||||
class BaseHandler:
|
||||
|
@ -122,42 +155,51 @@ class BaseHandler:
|
|||
def done(self) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def generic_handler(processor: Any, output: MutableSequence[str], fence: str, lang: str) -> BaseHandler:
|
||||
def generic_handler(processor: Any, output: MutableSequence[str],
|
||||
fence: str, lang: str,
|
||||
run_content_validators: Optional[bool]=False) -> BaseHandler:
|
||||
if lang in ('quote', 'quoted'):
|
||||
return QuoteHandler(processor, output, fence)
|
||||
elif lang in ('math', 'tex', 'latex'):
|
||||
return TexHandler(processor, output, fence)
|
||||
else:
|
||||
return CodeHandler(processor, output, fence, lang)
|
||||
return CodeHandler(processor, output, fence, lang, run_content_validators)
|
||||
|
||||
def check_for_new_fence(processor: Any, output: MutableSequence[str], line: str) -> None:
|
||||
def check_for_new_fence(processor: Any, output: MutableSequence[str], line: str,
|
||||
run_content_validators: Optional[bool]=False) -> None:
|
||||
m = FENCE_RE.match(line)
|
||||
if m:
|
||||
fence = m.group('fence')
|
||||
lang = m.group('lang')
|
||||
handler = generic_handler(processor, output, fence, lang)
|
||||
|
||||
handler = generic_handler(processor, output, fence, lang, run_content_validators)
|
||||
processor.push(handler)
|
||||
else:
|
||||
output.append(line)
|
||||
|
||||
class OuterHandler(BaseHandler):
|
||||
def __init__(self, processor: Any, output: MutableSequence[str]) -> None:
|
||||
def __init__(self, processor: Any, output: MutableSequence[str],
|
||||
run_content_validators: Optional[bool]=False) -> None:
|
||||
self.output = output
|
||||
self.processor = processor
|
||||
self.run_content_validators = run_content_validators
|
||||
|
||||
def handle_line(self, line: str) -> None:
|
||||
check_for_new_fence(self.processor, self.output, line)
|
||||
check_for_new_fence(self.processor, self.output, line,
|
||||
self.run_content_validators)
|
||||
|
||||
def done(self) -> None:
|
||||
self.processor.pop()
|
||||
|
||||
class CodeHandler(BaseHandler):
|
||||
def __init__(self, processor: Any, output: MutableSequence[str], fence: str, lang: str) -> None:
|
||||
def __init__(self, processor: Any, output: MutableSequence[str],
|
||||
fence: str, lang: str, run_content_validators: Optional[bool]=False) -> None:
|
||||
self.processor = processor
|
||||
self.output = output
|
||||
self.fence = fence
|
||||
self.lang = lang
|
||||
self.lines = [] # type: List[str]
|
||||
self.run_content_validators = run_content_validators
|
||||
|
||||
def handle_line(self, line: str) -> None:
|
||||
if line.rstrip() == self.fence:
|
||||
|
@ -167,6 +209,12 @@ class CodeHandler(BaseHandler):
|
|||
|
||||
def done(self) -> None:
|
||||
text = '\n'.join(self.lines)
|
||||
|
||||
# run content validators (if any)
|
||||
if self.run_content_validators:
|
||||
validator = CODE_VALIDATORS.get(self.lang, lambda text: None)
|
||||
validator(self.lines)
|
||||
|
||||
text = self.processor.format_code(self.lang, text)
|
||||
text = self.processor.placeholder(text)
|
||||
processed_lines = text.split('\n')
|
||||
|
@ -222,10 +270,11 @@ class TexHandler(BaseHandler):
|
|||
|
||||
|
||||
class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
|
||||
def __init__(self, md: markdown.Markdown) -> None:
|
||||
def __init__(self, md: markdown.Markdown, run_content_validators: Optional[bool]=False) -> None:
|
||||
markdown.preprocessors.Preprocessor.__init__(self, md)
|
||||
|
||||
self.checked_for_codehilite = False
|
||||
self.run_content_validators = run_content_validators
|
||||
self.codehilite_conf = {} # type: Dict[str, List[Any]]
|
||||
|
||||
def push(self, handler: BaseHandler) -> None:
|
||||
|
@ -242,7 +291,7 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
|
|||
processor = self
|
||||
self.handlers = [] # type: List[BaseHandler]
|
||||
|
||||
handler = OuterHandler(processor, output)
|
||||
handler = OuterHandler(processor, output, self.run_content_validators)
|
||||
self.push(handler)
|
||||
|
||||
for line in lines:
|
||||
|
@ -324,7 +373,7 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
|
|||
|
||||
|
||||
def makeExtension(*args: Any, **kwargs: None) -> FencedCodeExtension:
|
||||
return FencedCodeExtension(*args, **kwargs)
|
||||
return FencedCodeExtension(kwargs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
|
|
@ -103,7 +103,9 @@ def render_markdown_path(markdown_file_path: str,
|
|||
linenums=False,
|
||||
guess_lang=False
|
||||
),
|
||||
zerver.lib.bugdown.fenced_code.makeExtension(),
|
||||
zerver.lib.bugdown.fenced_code.makeExtension(
|
||||
run_content_validators=context.get('run_content_validators', False)
|
||||
),
|
||||
zerver.lib.bugdown.api_arguments_table_generator.makeExtension(
|
||||
base_path='templates/zerver/api/'),
|
||||
zerver.lib.bugdown.api_code_examples.makeExtension(),
|
||||
|
|
|
@ -1709,6 +1709,50 @@ class BugdownErrorTests(ZulipTestCase):
|
|||
with self.assertRaises(BugdownRenderingException):
|
||||
bugdown_convert(msg)
|
||||
|
||||
def test_curl_code_block_validation(self) -> None:
|
||||
processor = bugdown.fenced_code.FencedBlockPreprocessor(None)
|
||||
processor.run_content_validators = True
|
||||
|
||||
# Simulate code formatting.
|
||||
processor.format_code = lambda lang, code: lang + ':' + code # type: ignore # mypy doesn't allow monkey-patching functions
|
||||
processor.placeholder = lambda s: '**' + s.strip('\n') + '**' # type: ignore # https://github.com/python/mypy/issues/708
|
||||
|
||||
markdown = [
|
||||
'``` curl',
|
||||
'curl {{ api_url }}/v1/register',
|
||||
' -u BOT_EMAIL_ADDRESS:BOT_API_KEY',
|
||||
' -d "queue_id=1375801870:2942"',
|
||||
'```',
|
||||
]
|
||||
|
||||
with self.assertRaises(BugdownRenderingException):
|
||||
processor.run(markdown)
|
||||
|
||||
def test_curl_code_block_without_validation(self) -> None:
|
||||
processor = bugdown.fenced_code.FencedBlockPreprocessor(None)
|
||||
|
||||
# Simulate code formatting.
|
||||
processor.format_code = lambda lang, code: lang + ':' + code # type: ignore # mypy doesn't allow monkey-patching functions
|
||||
processor.placeholder = lambda s: '**' + s.strip('\n') + '**' # type: ignore # https://github.com/python/mypy/issues/708
|
||||
|
||||
markdown = [
|
||||
'``` curl',
|
||||
'curl {{ api_url }}/v1/register',
|
||||
' -u BOT_EMAIL_ADDRESS:BOT_API_KEY',
|
||||
' -d "queue_id=1375801870:2942"',
|
||||
'```',
|
||||
]
|
||||
expected = [
|
||||
'',
|
||||
'**curl:curl {{ api_url }}/v1/register',
|
||||
' -u BOT_EMAIL_ADDRESS:BOT_API_KEY',
|
||||
' -d "queue_id=1375801870:2942"**',
|
||||
'',
|
||||
''
|
||||
]
|
||||
|
||||
result = processor.run(markdown)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
class BugdownAvatarTestCase(ZulipTestCase):
|
||||
def test_possible_avatar_emails(self) -> None:
|
||||
|
|
|
@ -89,6 +89,22 @@ class DocPageTest(ZulipTestCase):
|
|||
if not doc_html_str:
|
||||
self.assert_in_success_response(['<meta name="robots" content="noindex,nofollow">'], result)
|
||||
|
||||
@slow("Tests dozens of endpoints")
|
||||
def test_api_doc_endpoints(self) -> None:
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
api_docs_dir = os.path.join(current_dir, '..', '..', 'templates/zerver/api/')
|
||||
files = os.listdir(api_docs_dir)
|
||||
|
||||
def _filter_func(fp: str) -> bool:
|
||||
ignored_files = ['sidebar_index.md', 'index.md', 'missing.md']
|
||||
return fp.endswith('.md') and fp not in ignored_files
|
||||
|
||||
files = list(filter(_filter_func, files))
|
||||
|
||||
for f in files:
|
||||
endpoint = '/api/{}'.format(os.path.splitext(f)[0])
|
||||
self._test(endpoint, '', doc_html_str=True)
|
||||
|
||||
@slow("Tests dozens of endpoints, including generating lots of emails")
|
||||
def test_doc_endpoints(self) -> None:
|
||||
self._test('/api/', 'The Zulip API')
|
||||
|
|
|
@ -124,6 +124,7 @@ class MarkdownDirectoryView(ApiURLView):
|
|||
# An "article" might require the api_uri_context to be rendered
|
||||
api_uri_context = {} # type: Dict[str, Any]
|
||||
add_api_uri_context(api_uri_context, self.request)
|
||||
api_uri_context["run_content_validators"] = True
|
||||
context["api_uri_context"] = api_uri_context
|
||||
return context
|
||||
|
||||
|
|
Loading…
Reference in New Issue