Page MenuHomeSoftware Heritage

test_apidoc.py
No OneTemporary

test_apidoc.py

# Copyright (C) 2015-2019 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
import textwrap
import pytest
from rest_framework.response import Response
from swh.storage.exc import StorageDBError, StorageAPIError
from swh.web.api.apidoc import api_doc, _parse_httpdomain_doc
from swh.web.api.apiurls import api_route
from swh.web.common.exc import BadInputExc, ForbiddenExc, NotFoundExc
from swh.web.common.utils import reverse, prettify_html
from swh.web.tests.django_asserts import assert_template_used
_httpdomain_doc = """
.. http:get:: /api/1/revision/(sha1_git)/
Get information about a revision in the archive.
Revisions are identified by **sha1** checksums, compatible with Git commit
identifiers.
See :func:`swh.model.identifiers.revision_identifier` in our data model
module for details about how they are computed.
:param string sha1_git: hexadecimal representation of the revision
**sha1_git** identifier
:reqheader Accept: the requested response content type,
either ``application/json`` (default) or ``application/yaml``
:resheader Content-Type: this depends on :http:header:`Accept` header
of request
:<json int n: sample input integer
:<json string s: sample input string
:<json array a: sample input array
:>json object author: information about the author of the revision
:>json object committer: information about the committer of the revision
:>json string committer_date: ISO representation of the commit date
(in UTC)
:>json string date: ISO representation of the revision date (in UTC)
:>json string directory: the unique identifier that revision points to
:>json string directory_url: link to
:http:get:`/api/1/directory/(sha1_git)/[(path)/]` to get information
about the directory associated to the revision
:>json string id: the revision unique identifier
:>json boolean merge: whether or not the revision corresponds to a merge
commit
:>json string message: the message associated to the revision
:>json array parents: the parents of the revision, i.e. the previous
revisions that head directly to it, each entry of that array contains
an unique parent revision identifier but also a link to
:http:get:`/api/1/revision/(sha1_git)/` to get more information
about it
:>json string type: the type of the revision
**Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`
:statuscode 200: no error
:statuscode 400: an invalid **sha1_git** value has been provided
:statuscode 404: requested revision can not be found in the archive
**Request:**
.. parsed-literal::
:swh_web_api:`revision/aafb16d69fd30ff58afdd69036a26047f3aebdc6/`
"""
_exception_http_code = {
BadInputExc: 400,
ForbiddenExc: 403,
NotFoundExc: 404,
Exception: 500,
StorageAPIError: 503,
StorageDBError: 503,
}
def test_apidoc_nodoc_failure():
with pytest.raises(Exception):
@api_doc('/my/nodoc/url/')
def apidoc_nodoc_tester(request, arga=0, argb=0):
return Response(arga + argb)
@api_route(r'/some/(?P<myarg>[0-9]+)/(?P<myotherarg>[0-9]+)/',
'api-1-some-doc-route')
@api_doc('/some/doc/route/')
def apidoc_route(request, myarg, myotherarg, akw=0):
"""
Sample doc
"""
return {'result': int(myarg) + int(myotherarg) + akw}
def test_apidoc_route_doc(client):
url = reverse('api-1-some-doc-route-doc')
rv = client.get(url, HTTP_ACCEPT='text/html')
assert rv.status_code == 200, rv.content
assert_template_used(rv, 'api/apidoc.html')
def test_apidoc_route_fn(api_client):
url = reverse('api-1-some-doc-route',
url_args={'myarg': 1, 'myotherarg': 1})
rv = api_client.get(url)
assert rv.status_code == 200, rv.data
@api_route(r'/test/error/(?P<exc_name>.+)/', 'api-1-test-error')
@api_doc('/test/error/')
def apidoc_test_error_route(request, exc_name):
"""
Sample doc
"""
for e in _exception_http_code.keys():
if e.__name__ == exc_name:
raise e('Error')
def test_apidoc_error(api_client):
for exc, code in _exception_http_code.items():
url = reverse('api-1-test-error',
url_args={'exc_name': exc.__name__})
rv = api_client.get(url)
assert rv.status_code == code, rv.data
@api_route(r'/some/full/(?P<myarg>[0-9]+)/(?P<myotherarg>[0-9]+)/',
'api-1-some-complete-doc-route')
@api_doc('/some/complete/doc/route/')
def apidoc_full_stack(request, myarg, myotherarg, akw=0):
"""
Sample doc
"""
return {'result': int(myarg) + int(myotherarg) + akw}
def test_apidoc_full_stack_doc(client):
url = reverse('api-1-some-complete-doc-route-doc')
rv = client.get(url, HTTP_ACCEPT='text/html')
assert rv.status_code == 200, rv.content
assert_template_used(rv, 'api/apidoc.html')
def test_apidoc_full_stack_fn(api_client):
url = reverse('api-1-some-complete-doc-route',
url_args={'myarg': 1, 'myotherarg': 1})
rv = api_client.get(url)
assert rv.status_code == 200, rv.data
@api_route(r'/test/post/only/', 'api-1-test-post-only',
methods=['POST'])
@api_doc('/test/post/only/')
def apidoc_test_post_only(request, exc_name):
"""
Sample doc
"""
return {'result': 'some data'}
def test_apidoc_post_only(client):
# a dedicated view accepting GET requests should have
# been created to display the HTML documentation
url = reverse('api-1-test-post-only-doc')
rv = client.get(url, HTTP_ACCEPT='text/html')
assert rv.status_code == 200, rv.content
assert_template_used(rv, 'api/apidoc.html')
def test_api_doc_parse_httpdomain():
doc_data = {
'description': '',
'urls': [],
'args': [],
'params': [],
'resheaders': [],
'reqheaders': [],
'input_type': '',
'inputs': [],
'return_type': '',
'returns': [],
'status_codes': [],
'examples': []
}
_parse_httpdomain_doc(_httpdomain_doc, doc_data)
expected_urls = [{
'rule': '/api/1/revision/ **\\(sha1_git\\)** /',
'methods': ['GET', 'HEAD']
}]
assert 'urls' in doc_data
assert doc_data['urls'] == expected_urls
expected_description = ('Get information about a revision in the archive. '
'Revisions are identified by **sha1** checksums, '
'compatible with Git commit identifiers. See '
'**swh.model.identifiers.revision_identifier** in '
'our data model module for details about how they '
'are computed.')
assert 'description' in doc_data
assert doc_data['description'] == expected_description
expected_args = [{
'name': 'sha1_git',
'type': 'string',
'doc': ('hexadecimal representation of the revision '
'**sha1_git** identifier')
}]
assert 'args' in doc_data
assert doc_data['args'] == expected_args
expected_params = []
assert 'params' in doc_data
assert doc_data['params'] == expected_params
expected_reqheaders = [{
'doc': ('the requested response content type, either '
'``application/json`` (default) or ``application/yaml``'),
'name': 'Accept'
}]
assert 'reqheaders' in doc_data
assert doc_data['reqheaders'] == expected_reqheaders
expected_resheaders = [{
'doc': 'this depends on **Accept** header of request',
'name': 'Content-Type'
}]
assert 'resheaders' in doc_data
assert doc_data['resheaders'] == expected_resheaders
expected_statuscodes = [
{
'code': '200',
'doc': 'no error'
},
{
'code': '400',
'doc': 'an invalid **sha1_git** value has been provided'
},
{
'code': '404',
'doc': 'requested revision can not be found in the archive'
}
]
assert 'status_codes' in doc_data
assert doc_data['status_codes'] == expected_statuscodes
expected_input_type = 'object'
assert 'input_type' in doc_data
assert doc_data['input_type'] == expected_input_type
expected_inputs = [
{
'name': 'n',
'type': 'int',
'doc': 'sample input integer'
},
{
'name': 's',
'type': 'string',
'doc': 'sample input string'
},
{
'name': 'a',
'type': 'array',
'doc': 'sample input array'
},
]
assert 'inputs' in doc_data
assert doc_data['inputs'] == expected_inputs
expected_return_type = 'object'
assert 'return_type' in doc_data
assert doc_data['return_type'] == expected_return_type
expected_returns = [
{
'name': 'author',
'type': 'object',
'doc': 'information about the author of the revision'
},
{
'name': 'committer',
'type': 'object',
'doc': 'information about the committer of the revision'
},
{
'name': 'committer_date',
'type': 'string',
'doc': 'ISO representation of the commit date (in UTC)'
},
{
'name': 'date',
'type': 'string',
'doc': 'ISO representation of the revision date (in UTC)'
},
{
'name': 'directory',
'type': 'string',
'doc': 'the unique identifier that revision points to'
},
{
'name': 'directory_url',
'type': 'string',
'doc': ('link to `</api/1/directory/>`_ to get information about '
'the directory associated to the revision')
},
{
'name': 'id',
'type': 'string',
'doc': 'the revision unique identifier'
},
{
'name': 'merge',
'type': 'boolean',
'doc': 'whether or not the revision corresponds to a merge commit'
},
{
'name': 'message',
'type': 'string',
'doc': 'the message associated to the revision'
},
{
'name': 'parents',
'type': 'array',
'doc': ('the parents of the revision, i.e. the previous revisions '
'that head directly to it, each entry of that array '
'contains an unique parent revision identifier but also a '
'link to `</api/1/revision/>`_ to get more information '
'about it')
},
{
'name': 'type',
'type': 'string',
'doc': 'the type of the revision'
}
]
assert 'returns' in doc_data
assert doc_data['returns'] == expected_returns
expected_examples = [
'/api/1/revision/aafb16d69fd30ff58afdd69036a26047f3aebdc6/'
]
assert 'examples' in doc_data
assert doc_data['examples'] == expected_examples
@api_route(r'/post/endpoint/', 'api-1-post-endpoint',
methods=['POST'])
@api_doc('/post/endpoint/')
def apidoc_test_post_endpoint(request):
"""
.. http:post:: /api/1/post/endpoint/
Endpoint documentation
:<jsonarr string -: Input array of pids
:>json object <swh_pid>: an object whose keys are input persistent
identifiers and values objects with the following keys:
* **known (bool)**: whether the object was found
"""
pass
def test_apidoc_input_output_doc(client):
url = reverse('api-1-post-endpoint-doc')
rv = client.get(url, HTTP_ACCEPT='text/html')
assert rv.status_code == 200, rv.content
assert_template_used(rv, 'api/apidoc.html')
input_html_doc = textwrap.indent((
'<dl class="row">\n'
' <dt class="col col-md-2 text-right">\n'
' array\n'
' </dt>\n'
' <dd class="col col-md-9">\n'
' <p>\n'
' Input array of pids\n'
' </p>\n'
' </dd>\n'
'</dl>\n'
), ' '*7)
output_html_doc = textwrap.indent((
'<dl class="row">\n'
' <dt class="col col-md-2 text-right">\n'
' object\n'
' </dt>\n'
' <dd class="col col-md-9">\n'
' <p>\n'
' an object containing the following keys:\n'
' </p>\n'
' <div class="swh-rst">\n'
' <blockquote>\n'
' <ul>\n'
' <li>\n'
' <p>\n'
' <strong>\n'
' &lt;swh_pid&gt; (object)\n'
' </strong>\n'
' : an object whose keys are input persistent identifiers'
' and values objects with the following keys:\n'
' </p>\n'
' <blockquote>\n'
' <ul class="simple">\n'
' <li>\n'
' <p>\n'
' <strong>\n'
' known (bool)\n'
' </strong>\n'
' : whether the object was found\n'
' </p>\n'
' </li>\n'
' </ul>\n'
' </blockquote>\n'
' </li>\n'
' </ul>\n'
' </blockquote>\n'
' </div>\n'
' </dd>\n'
'</dl>\n'
), ' '*7)
html = prettify_html(rv.content)
assert input_html_doc in html
assert output_html_doc in html

File Metadata

Mime Type
text/x-python
Expires
Sat, Jun 21, 5:47 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3344125

Event Timeline