Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7124781
D114.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
27 KB
Subscribers
None
D114.diff
View Options
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
Details
Attached
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
Attached To
D114: apidoc: harden the decorator stack
Event Timeline
Log In to Comment