Page MenuHomeSoftware Heritage

D114.diff
No OneTemporary

D114.diff

diff --git a/README-dev.md b/README-dev.md
--- a/README-dev.md
+++ b/README-dev.md
@@ -41,6 +41,12 @@
Used by `api`
- utils Utilities used throughout swh-web-ui.
+### About apidoc
+
+This is a 'decorator tower' that stores the data associated with the documentation upon loading
+the apidoc module. The top decorator of any tower should be @apidoc.route(). Apidoc raises an
+exception if this decorator is missing, and flask raises an exception if it is present but not at
+the top of the tower.
## Graphics summary
diff --git a/swh/web/ui/apidoc.py b/swh/web/ui/apidoc.py
--- a/swh/web/ui/apidoc.py
+++ b/swh/web/ui/apidoc.py
@@ -96,18 +96,88 @@
cls.apidoc_routes[route] = docstring
-class route(object): # noqa: N801
+class APIDocException(Exception):
+ """
+ Custom exception to signal errors in the use of the APIDoc decorators
+ """
+
+
+class APIDocBase(object):
+ """
+ The API documentation decorator base class, responsible for the
+ operations that link the decorator stack together:
+
+ * manages the _inner_dec property, which represents the
+ decorator directly below self in the decorator tower
+ * contains the logic used to return appropriately if self is the last
+ decorator to be applied to the API function
+ """
+
+ def __init__(self):
+ self._inner_dec = None
+
+ @property
+ def inner_dec(self):
+ return self._inner_dec
+
+ @inner_dec.setter
+ def inner_dec(self, instance):
+ self._inner_dec = instance
+
+ @property
+ def data(self):
+ raise NotImplementedError
+
+ def process_rv(self, f, args, kwargs):
+ """
+ From the arguments f has, determine whether or not it is the last
+ decorator in the stack, and return the appropriate call to f.
+ """
+
+ rv = None
+ if 'outer_decorator' in f.__code__.co_varnames:
+ rv = f(*args, **kwargs)
+ else:
+ nargs = {k: v for k, v in kwargs.items() if k != 'outer_decorator'}
+ if f.__code__.co_argcount > 0 and (args, nargs) == ((), {}):
+ rv = None # Documentation call
+ else:
+ rv = f(*args, **nargs)
+ return rv
+
+ def maintain_stack(self, f, args, kwargs):
+ """
+ From the arguments f is called with, determine whether or not the
+ stack link was made by @apidoc.route, and maintain the linking for
+ the next call to f.
+ """
+
+ if 'outer_decorator' not in kwargs:
+ raise APIDocException('Apidoc %s: expected an apidoc'
+ ' route decorator first'
+ % self.__class__.__name__)
+ kwargs['outer_decorator'].inner_dec = self
+ kwargs['outer_decorator'] = self
+
+ return self.process_rv(f, args, kwargs)
+
+
+class route(APIDocBase): # noqa: N801
"""
Decorate an API method to register it in the API doc route index
and create the corresponding Flask route.
- Caution: decorating a method with this requires to also decorate it
- __at least__ with @returns, or breaks the decorated endpoint
+
+ This decorator is responsible for bootstrapping the linking of subsequent
+ decorators, as well as traversing the decorator stack to obtain the
+ documentation data from it.
+
Args:
route: the documentation page's route
noargs: set to True if the route has no arguments, and its result
should be displayed anytime its documentation is requested
"""
def __init__(self, route, noargs=False):
+ super().__init__()
self.route = route
self.noargs = noargs
@@ -116,17 +186,77 @@
@wraps(f)
def doc_func(*args, **kwargs):
- return f(call_args=(args, kwargs),
- doc_route=self.route,
- noargs=self.noargs)
+ kwargs['outer_decorator'] = self
+ rv = self.process_rv(f, args, kwargs)
+ return self.compute_return(f, rv)
if not self.noargs:
app.add_url_rule(self.route, f.__name__, doc_func)
return doc_func
+ def filter_api_url(self, endpoint, route_re, noargs):
+ doc_methods = {'GET', 'HEAD', 'OPTIONS'}
+ if re.match(route_re, endpoint['rule']):
+ if endpoint['methods'] == doc_methods and not noargs:
+ return False
+ return True
+
+ def compute_return(self, f, rv):
+ # Build documentation
+ data = self.data
+ if not f.__doc__:
+ raise APIDocException('Apidoc %s: expected a docstring'
+ ' for function %s'
+ % (self.__class__.__name__, f.__name__))
+ data['docstring'] = f.__doc__
+
+ route_re = re.compile('.*%s$' % data['route'])
+ endpoint_list = APIUrls.get_method_endpoints(f.__name__)
+ other_urls = [url for url in endpoint_list if
+ self.filter_api_url(url, route_re, data['noargs'])]
+ data['urls'] = other_urls
+
+ # Build example endpoint URL
+ if 'args' in data:
+ defaults = {arg['name']: arg['default'] for arg in data['args']}
+ example = url_for(f.__name__, **defaults)
+ data['example'] = re.sub(r'(.*)\?.*', r'\1', example)
+
+ # Prepare and send to mimetype selector if it's not a doc request
+ if re.match(route_re, request.url) and not data['noargs'] \
+ and request.method == 'GET':
+ return app.response_class(
+ render_template('apidoc.html', **data),
+ content_type='text/html')
+
+ g.doc_env = data # Store for response processing
+ return rv
+
+ @property
+ def data(self):
+ data = {'route': self.route, 'noargs': self.noargs}
+
+ doc_instance = self.inner_dec
+ while doc_instance:
+ if isinstance(doc_instance, arg):
+ if 'args' not in data:
+ data['args'] = []
+ data['args'].append(doc_instance.data)
+ elif isinstance(doc_instance, raises):
+ if 'excs' not in data:
+ data['excs'] = []
+ data['excs'].append(doc_instance.data)
+ elif isinstance(doc_instance, returns):
+ data['return'] = doc_instance.data
+ else:
+ raise APIDocException('Unknown API documentation decorator')
+ doc_instance = doc_instance.inner_dec
+
+ return data
+
-class arg(object): # noqa: N801
+class arg(APIDocBase): # noqa: N801
"""
Decorate an API method to display an argument's information on the doc
page specified by @route above.
@@ -138,25 +268,28 @@
argdoc: the argument's documentation string
"""
def __init__(self, name, default, argtype, argdoc):
+ super().__init__()
self.doc_dict = {
'name': name,
'type': argtype.value,
'doc': argdoc,
'default': default
}
+ self.inner_dec = None
def __call__(self, f):
@wraps(f)
- def arg_fun(*args, **kwargs):
- if 'args' in kwargs:
- kwargs['args'].append(self.doc_dict)
- else:
- kwargs['args'] = [self.doc_dict]
- return f(*args, **kwargs)
+ def arg_fun(*args, outer_decorator=None, **kwargs):
+ kwargs['outer_decorator'] = outer_decorator
+ return self.maintain_stack(f, args, kwargs)
return arg_fun
+ @property
+ def data(self):
+ return self.doc_dict
-class raises(object): # noqa: N801
+
+class raises(APIDocBase): # noqa: N801
"""
Decorate an API method to display information pertaining to an exception
that can be raised by this method.
@@ -165,6 +298,7 @@
doc: the exception's documentation string
"""
def __init__(self, exc, doc):
+ super().__init__()
self.exc_dict = {
'exc': exc.value,
'doc': doc
@@ -172,71 +306,37 @@
def __call__(self, f):
@wraps(f)
- def exc_fun(*args, **kwargs):
- if 'excs' in kwargs:
- kwargs['excs'].append(self.exc_dict)
- else:
- kwargs['excs'] = [self.exc_dict]
- return f(*args, **kwargs)
+ def exc_fun(*args, outer_decorator=None, **kwargs):
+ kwargs['outer_decorator'] = outer_decorator
+ return self.maintain_stack(f, args, kwargs)
return exc_fun
+ @property
+ def data(self):
+ return self.exc_dict
+
-class returns(object): # noqa: N801
+class returns(APIDocBase): # noqa: N801
"""
Decorate an API method to display information about its return value.
- Caution: this MUST be the last decorator in the apidoc decorator stack,
- or the decorated endpoint breaks
Args:
rettype: the return value's type as an Enum value from apidoc.rettypes
retdoc: the return value's documentation string
"""
def __init__(self, rettype=None, retdoc=None):
+ super().__init__()
self.return_dict = {
'type': rettype.value,
'doc': retdoc
}
- def filter_api_url(self, endpoint, route_re, noargs):
- doc_methods = {'GET', 'HEAD', 'OPTIONS'}
- if re.match(route_re, endpoint['rule']):
- if endpoint['methods'] == doc_methods and not noargs:
- return False
- return True
-
def __call__(self, f):
@wraps(f)
- def ret_fun(*args, **kwargs):
- # Build documentation
- env = {
- 'docstring': f.__doc__,
- 'route': kwargs['doc_route'],
- 'return': self.return_dict
- }
-
- for arg in ['args', 'excs']:
- if arg in kwargs:
- env[arg] = kwargs[arg]
-
- route_re = re.compile('.*%s$' % kwargs['doc_route'])
- endpoint_list = APIUrls.get_method_endpoints(f.__name__)
- other_urls = [url for url in endpoint_list if
- self.filter_api_url(url, route_re, kwargs['noargs'])]
- env['urls'] = other_urls
-
- # Build example endpoint URL
- if 'args' in env:
- defaults = {arg['name']: arg['default'] for arg in env['args']}
- example = url_for(f.__name__, **defaults)
- env['example'] = re.sub(r'(.*)\?.*', r'\1', example)
-
- # Prepare and send to mimetype selector if it's not a doc request
- if re.match(route_re, request.url) and not kwargs['noargs'] \
- and request.method == 'GET':
- return app.response_class(
- render_template('apidoc.html', **env),
- content_type='text/html')
-
- cargs, ckwargs = kwargs['call_args']
- g.doc_env = env # Store for response processing
- return f(*cargs, **ckwargs)
+ def ret_fun(*args, outer_decorator=None, **kwargs):
+ kwargs['outer_decorator'] = outer_decorator
+ return self.maintain_stack(f, args, kwargs)
return ret_fun
+
+ @property
+ def data(self):
+ return self.return_dict
diff --git a/swh/web/ui/renderers.py b/swh/web/ui/renderers.py
--- a/swh/web/ui/renderers.py
+++ b/swh/web/ui/renderers.py
@@ -39,7 +39,7 @@
class SWHMultiResponse(Response, SWHFilterEnricher):
"""
A Flask Response subclass.
- Override force_type to transform dict responses into callable Flask
+ Override force_type to transform dict/list responses into callable Flask
response objects whose mimetype matches the request's Accept header: HTML
template render, YAML dump or default to a JSON dump.
"""
@@ -62,7 +62,7 @@
rv = cls.filter_by_fields(cls, rv)
acc_mime = ['application/json', 'application/yaml', 'text/html']
best_match = request.accept_mimetypes.best_match(acc_mime)
- # return a template render
+
if wants_html(best_match):
data = json.dumps(rv, sort_keys=True,
indent=4, separators=(',', ': '))
@@ -72,13 +72,11 @@
rv = Response(render_template('apidoc.html', **env),
content_type='text/html',
**options)
- # return formatted yaml
elif wants_yaml(best_match):
rv = Response(
yaml.dump(rv),
content_type='application/yaml',
**options)
- # return formatted json
else:
# jsonify is unhappy with lists in Flask 0.10.1, use json.dumps
rv = Response(
diff --git a/swh/web/ui/tests/test_apidoc.py b/swh/web/ui/tests/test_apidoc.py
--- a/swh/web/ui/tests/test_apidoc.py
+++ b/swh/web/ui/tests/test_apidoc.py
@@ -4,16 +4,17 @@
# See top-level LICENSE file for more information
-from unittest.mock import MagicMock, patch
-from nose.tools import istest
+from nose.tools import istest, nottest
from swh.web.ui import apidoc
-from swh.web.ui.tests import test_app
+from swh.web.ui.main import app
+from swh.web.ui.tests.test_app import SWHApidocTestCase
-class APIDocTestCase(test_app.SWHApidocTestCase):
+class APIDocTestCase(SWHApidocTestCase):
def setUp(self):
+
self.arg_dict = {
'name': 'my_pretty_arg',
'default': 'some default value',
@@ -37,310 +38,88 @@
'doc': 'a dict with amazing properties'
}
- @patch('swh.web.ui.apidoc.APIUrls')
- @patch('swh.web.ui.apidoc.app')
- @istest
- def apidoc_route(self, mock_app, mock_api_urls):
- # given
- decorator = apidoc.route('/some/url/for/doc/')
- mock_fun = MagicMock(return_value=123)
- mock_fun.__doc__ = 'Some documentation'
- mock_fun.__name__ = 'some_fname'
- decorated = decorator.__call__(mock_fun)
-
- # when
- decorated('some', 'value', kws='and a kw')
-
- # then
- mock_fun.assert_called_once_with(
- call_args=(('some', 'value'), {'kws': 'and a kw'}),
- doc_route='/some/url/for/doc/',
- noargs=False
- )
- mock_api_urls.index_add_route.assert_called_once_with(
- '/some/url/for/doc/',
- 'Some documentation')
- mock_app.add_url_rule.assert_called_once_with(
- '/some/url/for/doc/', 'some_fname', decorated)
+ @apidoc.route('/my/nodoc/url/')
+ @nottest
+ def apidoc_nodoc_tester(arga, argb):
+ return arga + argb
@istest
- def apidoc_arg_noprevious(self):
- # given
- decorator = apidoc.arg('my_pretty_arg',
- default='some default value',
- argtype=apidoc.argtypes.sha1,
- argdoc='this arg does things')
- mock_fun = MagicMock(return_value=123)
- decorated = decorator.__call__(mock_fun)
- self.arg_dict['type'] = self.arg_dict['type'].value
-
- # when
- decorated(call_args=((), {}), doc_route='some/route/')
-
- # then
- mock_fun.assert_called_once_with(
- call_args=((), {}),
- doc_route='some/route/',
- args=[self.arg_dict]
- )
+ def apidoc_nodoc_failure(self):
+ with self.assertRaises(Exception):
+ self.client.get('/my/nodoc/url/')
@istest
- def apidoc_arg_previous(self):
- # given
- decorator = apidoc.arg('my_other_arg',
- default='some other value',
- argtype=apidoc.argtypes.sha1,
- argdoc='this arg is optional')
- mock_fun = MagicMock(return_value=123)
- decorated = decorator.__call__(mock_fun)
-
- # when
- decorated(call_args=((), {}),
- doc_route='some/route/',
- args=[self.arg_dict])
-
- # then
- mock_fun.assert_called_once_with(
- call_args=((), {}),
- doc_route='some/route/',
- args=[self.arg_dict,
- {'name': 'my_other_arg',
- 'default': 'some other value',
- 'type': apidoc.argtypes.sha1.value,
- 'doc': 'this arg is optional'}])
+ def apidoc_badorder_failure(self):
+ with self.assertRaises(AssertionError):
+ @app.route('/my/badorder/url/<int:foo>/')
+ @apidoc.arg('foo',
+ default=True,
+ argtype=apidoc.argtypes.int,
+ argdoc='It\'s so fluffy!')
+ @apidoc.route('/my/badorder/url/')
+ @nottest
+ def apidoc_badorder_tester(foo, bar=0):
+ """
+ Some irrelevant doc since the decorators are bad
+ """
+ return foo + bar
+
+ @app.route('/some/<int:myarg>/<int:myotherarg>/')
+ @apidoc.route('/some/doc/route/')
+ @nottest
+ def apidoc_route_tester(myarg, myotherarg, akw=0):
+ """
+ Sample doc
+ """
+ return {'result': myarg + myotherarg + akw}
@istest
- def apidoc_raises_noprevious(self):
- # given
- decorator = apidoc.raises(exc=apidoc.excs.badinput,
- doc='My exception documentation')
- mock_fun = MagicMock(return_value=123)
- decorated = decorator.__call__(mock_fun)
- self.stub_excs[0]['exc'] = self.stub_excs[0]['exc'].value
-
+ def apidoc_route_doc(self):
# when
- decorated(call_args=((), {}), doc_route='some/route/')
+ rv = self.client.get('/some/doc/route/')
# then
- mock_fun.assert_called_once_with(
- call_args=((), {}),
- doc_route='some/route/',
- excs=self.stub_excs
- )
+ self.assertEqual(rv.status_code, 200)
+ self.assert_template_used('apidoc.html')
@istest
- def apidoc_raises_previous(self):
- # given
- decorator = apidoc.raises(exc=apidoc.excs.notfound,
- doc='Another documentation')
- mock_fun = MagicMock(return_value=123)
- decorated = decorator.__call__(mock_fun)
- expected_excs = self.stub_excs + [{
- 'exc': apidoc.excs.notfound.value,
- 'doc': 'Another documentation'}]
- expected_excs[0]['exc'] = expected_excs[0]['exc'].value
+ def apidoc_route_fn(self):
# when
- decorated(call_args=((), {}),
- doc_route='some/route/',
- excs=self.stub_excs)
+ rv = self.client.get('/some/1/1/')
# then
- mock_fun.assert_called_once_with(
- call_args=((), {}),
- doc_route='some/route/',
- excs=expected_excs)
+ self.assertEqual(rv.status_code, 200)
+
+ @app.route('/some/full/<int:myarg>/<int:myotherarg>/')
+ @apidoc.route('/some/complete/doc/route/')
+ @apidoc.arg('myarg',
+ default=67,
+ argtype=apidoc.argtypes.int,
+ argdoc='my arg')
+ @apidoc.raises(exc=apidoc.excs.badinput, doc='Oops')
+ @apidoc.returns(rettype=apidoc.rettypes.dict,
+ retdoc='sum of args')
+ @nottest
+ def apidoc_full_stack_tester(myarg, myotherarg, akw=0):
+ """
+ Sample doc
+ """
+ return {'result': myarg + myotherarg + akw}
- @patch('swh.web.ui.apidoc.render_template')
- @patch('swh.web.ui.apidoc.url_for')
- @patch('swh.web.ui.apidoc.APIUrls')
- @patch('swh.web.ui.apidoc.request')
@istest
- def apidoc_returns_doc_call(self,
- mock_request,
- mock_api_urls,
- mock_url_for,
- mock_render):
- # given
- decorator = apidoc.returns(rettype=apidoc.rettypes.dict,
- retdoc='a dict with amazing properties')
- mock_fun = MagicMock(return_value=123)
- mock_fun.__name__ = 'some_fname'
- mock_fun.__doc__ = 'Some documentation'
- decorated = decorator.__call__(mock_fun)
-
- mock_api_urls.get_method_endpoints.return_value = self.stub_rule_list
-
- mock_request.url = 'http://my-domain.tld/some/doc/route/'
- mock_request.method = 'GET'
- mock_url_for.return_value = 'http://my-domain.tld/meaningful_route/'
-
- expected_env = {
- 'urls': [{'rule': 'some/route/with/args/',
- 'methods': {'GET', 'HEAD', 'OPTIONS'}},
- {'rule': 'some/other/route/',
- 'methods': {'GET', 'HEAD', 'OPTIONS'}}],
- 'docstring': 'Some documentation',
- 'args': self.stub_args,
- 'excs': self.stub_excs,
- 'route': 'some/doc/route/',
- 'example': 'http://my-domain.tld/meaningful_route/',
- 'return': self.stub_return
- }
-
+ def apidoc_full_stack_doc(self):
# when
- decorated(
- docstring='Some documentation',
- call_args=(('some', 'args'), {'kw': 'kwargs'}),
- args=self.stub_args,
- excs=self.stub_excs,
- doc_route='some/doc/route/',
- noargs=False
- )
+ rv = self.client.get('/some/complete/doc/route/')
# then
- self.assertEqual(mock_fun.call_args_list, []) # function not called
- mock_render.assert_called_once_with(
- 'apidoc.html',
- **expected_env
- )
+ self.assertEqual(rv.status_code, 200)
+ self.assert_template_used('apidoc.html')
- @patch('swh.web.ui.apidoc.g')
- @patch('swh.web.ui.apidoc.url_for')
- @patch('swh.web.ui.apidoc.APIUrls')
- @patch('swh.web.ui.apidoc.request')
@istest
- def apidoc_returns_noargs(self,
- mock_request,
- mock_api_urls,
- mock_url_for,
- mock_g):
-
- # given
- decorator = apidoc.returns(rettype=apidoc.rettypes.dict,
- retdoc='a dict with amazing properties')
- mock_fun = MagicMock(return_value=123)
- mock_fun.__name__ = 'some_fname'
- mock_fun.__doc__ = 'Some documentation'
- decorated = decorator.__call__(mock_fun)
-
- mock_api_urls.get_method_endpoints.return_value = [
- {'rule': 'some/doc/route/',
- 'methods': {'GET', 'HEAD', 'OPTIONS'}}]
- mock_request.url = 'http://my-domain.tld/some/doc/route/'
- doc_dict = {
- 'urls': [
- {'rule': 'some/doc/route/',
- 'methods': {'GET', 'HEAD', 'OPTIONS'}}],
- 'docstring': 'Some documentation',
- 'route': 'some/doc/route/',
- 'return': {'type': apidoc.rettypes.dict.value,
- 'doc': 'a dict with amazing properties'}
- }
-
- # when
- decorated(
- call_args=((), {}),
- doc_route='some/doc/route/',
- noargs=True
- )
-
- # then
- mock_fun.assert_called_once_with()
- self.assertEqual(mock_g.doc_env, doc_dict)
-
- @patch('swh.web.ui.apidoc.g')
- @patch('swh.web.ui.apidoc.url_for')
- @patch('swh.web.ui.apidoc.APIUrls')
- @patch('swh.web.ui.apidoc.request')
- @istest
- def apidoc_returns_same_fun(self,
- mock_request,
- mock_api_urls,
- mock_url_for,
- mock_g):
-
- # given
- decorator = apidoc.returns(rettype=apidoc.rettypes.dict,
- retdoc='a dict with amazing properties')
- mock_fun = MagicMock(return_value=123)
- mock_fun.__name__ = 'some_fname'
- mock_fun.__doc__ = 'Some documentation'
- decorated = decorator.__call__(mock_fun)
-
- mock_api_urls.get_method_endpoints.return_value = [
- {'rule': 'some/doc/route/',
- 'methods': {'GET', 'HEAD', 'OPTIONS'}},
- {'rule': 'some/doc/route/',
- 'methods': {'POST'}}]
- mock_request.url = 'http://my-domain.tld/some/doc/route/'
- mock_request.method = 'POST'
- doc_dict = {
- 'urls': [{'rule': 'some/doc/route/',
- 'methods': {'POST'}}],
- 'docstring': 'Some documentation',
- 'route': 'some/doc/route/',
- 'return': {'type': apidoc.rettypes.dict.value,
- 'doc': 'a dict with amazing properties'}
- }
-
- # when
- decorated(
- call_args=(('my', 'args'), {'kw': 'andkwargs'}),
- doc_route='some/doc/route/',
- noargs=False
- )
-
- # then
- mock_fun.assert_called_once_with('my', 'args', kw='andkwargs')
- self.assertEqual(mock_g.doc_env, doc_dict)
-
- @patch('swh.web.ui.apidoc.g')
- @patch('swh.web.ui.apidoc.url_for')
- @patch('swh.web.ui.apidoc.APIUrls')
- @patch('swh.web.ui.apidoc.request')
- @istest
- def apidoc_return_endpoint_call(self,
- mock_request,
- mock_api_urls,
- mock_url_for,
- mock_g):
- # given
- decorator = apidoc.returns(rettype=apidoc.rettypes.dict,
- retdoc='a dict with amazing properties')
- mock_fun = MagicMock(return_value=123)
- mock_fun.__name__ = 'some_fname'
- mock_fun.__doc__ = 'Some documentation'
- decorated = decorator.__call__(mock_fun)
-
- mock_api_urls.get_method_endpoints.return_value = self.stub_rule_list
-
- mock_request.url = 'http://my-domain.tld/some/arg/route/'
- mock_url_for.return_value = 'http://my-domain.tld/some/arg/route'
-
- doc_dict = {
- 'urls': [{'rule': 'some/route/with/args/',
- 'methods': {'GET', 'HEAD', 'OPTIONS'}},
- {'rule': 'some/other/route/',
- 'methods': {'GET', 'HEAD', 'OPTIONS'}}],
- 'docstring': 'Some documentation',
- 'args': self.stub_args,
- 'excs': self.stub_excs,
- 'route': 'some/doc/route/',
- 'example': 'http://my-domain.tld/some/arg/route',
- 'return': self.stub_return
- }
-
+ def apidoc_full_stack_fn(self):
# when
- decorated(
- docstring='Some documentation',
- call_args=(('some', 'args'), {'kw': 'kwargs'}),
- args=self.stub_args,
- excs=self.stub_excs,
- noargs=False,
- doc_route='some/doc/route/',
- )
+ rv = self.client.get('/some/full/1/1/')
# then
- mock_fun.assert_called_once_with('some', 'args', kw='kwargs')
- self.assertEqual(mock_g.doc_env, doc_dict)
+ self.assertEqual(rv.status_code, 200)
diff --git a/swh/web/ui/tests/test_app.py b/swh/web/ui/tests/test_app.py
--- a/swh/web/ui/tests/test_app.py
+++ b/swh/web/ui/tests/test_app.py
@@ -59,16 +59,6 @@
return main.app.test_client(), main.app.config, storage, main.app
-class SWHApidocTestCase(unittest.TestCase):
- """Testing APIDoc class.
-
- """
- @classmethod
- def setUpClass(cls):
- cls.app, cls.app_config, cls.storage, _ = create_app()
- cls.maxDiff = None
-
-
class SWHApiTestCase(unittest.TestCase):
"""Testing API class.
@@ -93,3 +83,9 @@
"""
_, _, _, appToDecorate = create_app()
return appToDecorate
+
+
+class SWHApidocTestCase(SWHViewTestCase, SWHApiTestCase):
+ """Testing APIDoc class.
+
+ """

File Metadata

Mime Type
text/plain
Expires
Dec 21 2024, 6:58 PM (11 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3220475

Event Timeline