diff --git a/swh/web/api/utils.py b/swh/web/api/utils.py index 722be7bd1..d3f7f610d 100644 --- a/swh/web/api/utils.py +++ b/swh/web/api/utils.py @@ -1,299 +1,213 @@ # Copyright (C) 2015-2018 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 from swh.web.common.utils import reverse from swh.web.common.query import parse_hash def filter_field_keys(data, field_keys): """Given an object instance (directory or list), and a csv field keys to filter on. Return the object instance with filtered keys. Note: Returns obj as is if it's an instance of types not in (dictionary, list) Args: - data: one object (dictionary, list...) to filter. - field_keys: csv or set of keys to filter the object on Returns: obj filtered on field_keys """ if isinstance(data, map): return map(lambda x: filter_field_keys(x, field_keys), data) if isinstance(data, list): return [filter_field_keys(x, field_keys) for x in data] if isinstance(data, dict): return {k: v for (k, v) in data.items() if k in field_keys} return data def person_to_string(person): """Map a person (person, committer, tagger, etc...) to a string. """ return ''.join([person['name'], ' <', person['email'], '>']) def enrich_object(object): """Enrich an object (revision, release) with link to the 'target' of type 'target_type'. Args: object: An object with target and target_type keys (e.g. release, revision) Returns: Object enriched with target_url pointing to the right swh.web.ui.api urls for the pointing object (revision, release, content, directory) """ obj = object.copy() if 'target' in obj and 'target_type' in obj: if obj['target_type'] == 'revision': obj['target_url'] = reverse('api-revision', url_args={'sha1_git': obj['target']}) elif obj['target_type'] == 'release': obj['target_url'] = reverse('api-release', url_args={'sha1_git': obj['target']}) elif obj['target_type'] == 'content': obj['target_url'] = \ reverse('api-content', url_args={'q': 'sha1_git:' + obj['target']}) elif obj['target_type'] == 'directory': obj['target_url'] = reverse('api-directory', url_args={'sha1_git': obj['target']}) if 'author' in obj: author = obj['author'] obj['author_url'] = reverse('api-person', url_args={'person_id': author['id']}) return obj enrich_release = enrich_object def enrich_directory(directory, context_url=None): """Enrich directory with url to content or directory. """ if 'type' in directory: target_type = directory['type'] target = directory['target'] if target_type == 'file': directory['target_url'] = \ reverse('api-content', url_args={'q': 'sha1_git:%s' % target}) if context_url: directory['file_url'] = context_url + directory['name'] + '/' elif target_type == 'dir': directory['target_url'] = reverse('api-directory', url_args={'sha1_git': target}) if context_url: directory['dir_url'] = context_url + directory['name'] + '/' else: directory['target_url'] = reverse('api-revision', url_args={'sha1_git': target}) if context_url: directory['rev_url'] = context_url + directory['name'] + '/' return directory def enrich_metadata_endpoint(content): """Enrich metadata endpoint with link to the upper metadata endpoint. """ c = content.copy() c['content_url'] = reverse('api-content', url_args={'q': 'sha1:%s' % c['id']}) return c def enrich_content(content, top_url=False, query_string=None): """Enrich content with links to: - data_url: its raw data - filetype_url: its filetype information - language_url: its programming language information - license_url: its licensing information Args: content: dict of data associated to a swh content object top_url: whether or not to include the content url in the enriched data query_string: optional query string of type '<algo>:<hash>' used when requesting the content, it acts as a hint for picking the same hash method when computing the url listed above Returns: An enriched content dict filled with additional urls """ checksums = content if 'checksums' in content: checksums = content['checksums'] hash_algo = 'sha1' if query_string: hash_algo = parse_hash(query_string)[0] if hash_algo in checksums: q = '%s:%s' % (hash_algo, checksums[hash_algo]) if top_url: content['content_url'] = reverse('api-content', url_args={'q': q}) content['data_url'] = reverse('api-content-raw', url_args={'q': q}) content['filetype_url'] = reverse('api-content-filetype', url_args={'q': q}) content['language_url'] = reverse('api-content-language', url_args={'q': q}) content['license_url'] = reverse('api-content-license', url_args={'q': q}) return content -def _get_path_list(path_string): - """Helper for enrich_revision: get a list of the sha1 id of the navigation - breadcrumbs, ordered from the oldest to the most recent. - - Args: - path_string: the path as a '/'-separated string - - Returns: - The navigation context as a list of sha1 revision ids - """ - return path_string.split('/') - - -def _get_revision_contexts(rev_id, context): - """Helper for enrich_revision: retrieve for the revision id and potentially - the navigation breadcrumbs the context to pass to parents and children of - of the revision. - - Args: - rev_id: the revision's sha1 id - context: the current navigation context - - Returns: - The context for parents, children and the url of the direct child as a - tuple in that order. - """ - context_for_parents = None - context_for_children = None - url_direct_child = None - - if not context: - return (rev_id, None, None) - - path_list = _get_path_list(context) - context_for_parents = '%s/%s' % (context, rev_id) - prev_for_children = path_list[:-1] - if len(prev_for_children) > 0: - context_for_children = '/'.join(prev_for_children) - child_id = path_list[-1] - # This commit is not the first commit in the path - if context_for_children: - url_direct_child = reverse('api-revision-context', - url_args={'sha1_git': child_id, - 'context': context_for_children}) - # This commit is the first commit in the path - else: - url_direct_child = reverse('api-revision', - url_args={'sha1_git': child_id}) - - return (context_for_parents, context_for_children, url_direct_child) - - -def _make_child_url(rev_children, context): - """Helper for enrich_revision: retrieve the list of urls corresponding - to the children of the current revision according to the navigation - breadcrumbs. - - Args: - rev_children: a list of revision id - context: the '/'-separated navigation breadcrumbs - - Returns: - the list of the children urls according to the context - """ - children = [] - for child in rev_children: - if context and child != _get_path_list(context)[-1]: - children.append(reverse('api-revision', - url_args={'sha1_git': child})) - elif not context: - children.append(reverse('api-revision', - url_args={'sha1_git': child})) - return children - - -def enrich_revision(revision, context=None): +def enrich_revision(revision): """Enrich revision with links where it makes sense (directory, parents). Keep track of the navigation breadcrumbs if they are specified. Args: revision: the revision as a dict - context: the navigation breadcrumbs as a /-separated string of revision - sha1_git """ - ctx_parents, ctx_children, url_direct_child = _get_revision_contexts( - revision['id'], context) - revision['url'] = reverse('api-revision', url_args={'sha1_git': revision['id']}) revision['history_url'] = reverse('api-revision-log', url_args={'sha1_git': revision['id']}) - if context: - revision['history_context_url'] = reverse( - 'api-revision-log', url_args={'sha1_git': revision['id'], - 'prev_sha1s': context}) if 'author' in revision: author = revision['author'] revision['author_url'] = reverse('api-person', url_args={'person_id': author['id']}) if 'committer' in revision: committer = revision['committer'] revision['committer_url'] = \ reverse('api-person', url_args={'person_id': committer['id']}) if 'directory' in revision: revision['directory_url'] = \ reverse('api-directory', url_args={'sha1_git': revision['directory']}) if 'parents' in revision: parents = [] for parent in revision['parents']: parents.append({ 'id': parent, 'url': reverse('api-revision', url_args={'sha1_git': parent}) }) revision['parents'] = parents if 'children' in revision: - children = _make_child_url(revision['children'], context) - if url_direct_child: - children.append(url_direct_child) + children = [] + for child in revision['children']: + children.append(reverse('api-revision', + url_args={'sha1_git': child})) revision['children_urls'] = children - else: - if url_direct_child: - revision['children_urls'] = [url_direct_child] if 'message_decoding_failed' in revision: revision['message_url'] = \ reverse('api-revision-raw-message', url_args={'sha1_git': revision['id']}) return revision diff --git a/swh/web/api/views/revision.py b/swh/web/api/views/revision.py index c64a80bd6..1083ce56c 100644 --- a/swh/web/api/views/revision.py +++ b/swh/web/api/views/revision.py @@ -1,501 +1,485 @@ # Copyright (C) 2015-2018 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 from django.http import HttpResponse from swh.web.common import service from swh.web.common.utils import reverse from swh.web.common.utils import parse_timestamp from swh.web.api import utils from swh.web.api.apidoc import api_doc from swh.web.api.apiurls import api_route from swh.web.api.views.utils import api_lookup def _revision_directory_by(revision, path, request_path, limit=100, with_data=False): """ Compute the revision matching criterion's directory or content data. Args: revision: dictionary of criterions representing a revision to lookup path: directory's path to lookup request_path: request path which holds the original context to limit: optional query parameter to limit the revisions log (default to 100). For now, note that this limit could impede the transitivity conclusion about sha1_git not being an ancestor of with_data: indicate to retrieve the content's raw data if path resolves to a content. """ def enrich_directory_local(dir, context_url=request_path): return utils.enrich_directory(dir, context_url) rev_id, result = service.lookup_directory_through_revision( revision, path, limit=limit, with_data=with_data) content = result['content'] if result['type'] == 'dir': # dir_entries result['content'] = list(map(enrich_directory_local, content)) else: # content result['content'] = utils.enrich_content(content) return result @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)/log/', 'api-revision-origin-log') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/log/', 'api-revision-origin-log') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/ts/(?P<ts>.+)/log/', 'api-revision-origin-log') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)' r'/ts/(?P<ts>.+)/log/', 'api-revision-origin-log') @api_doc('/revision/origin/log/') def api_revision_log_by(request, origin_id, branch_name='HEAD', ts=None): """ .. http:get:: /api/1/revision/origin/(origin_id)[/branch/(branch_name)][/ts/(timestamp)]/log Show the commit log for a revision, searching for it based on software origin, branch name, and/or visit timestamp. This endpoint behaves like :http:get:`/api/1/revision/(sha1_git)[/prev/(prev_sha1s)]/log/`, but operates on the revision that has been found at a given software origin, close to a given point in time, pointed by a given branch. :param int origin_id: a software origin identifier :param string branch_name: optional parameter specifying a fully-qualified branch name associated to the software origin, e.g., "refs/heads/master". Defaults to the HEAD branch. :param string timestamp: optional parameter specifying a timestamp close to which the revision pointed by the given branch should be looked up. The timestamp can be expressed either as an ISO date or as a Unix one (in UTC). Defaults to now. :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 :>jsonarr object author: information about the author of the revision :>jsonarr string author_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the author of the revision :>jsonarr object committer: information about the committer of the revision :>jsonarr string committer_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the committer of the revision :>jsonarr string committer_date: ISO representation of the commit date (in UTC) :>jsonarr string date: ISO representation of the revision date (in UTC) :>jsonarr string directory: the unique identifier that revision points to :>jsonarr string directory_url: link to :http:get:`/api/1/directory/(sha1_git)/[(path)/]` to get information about the directory associated to the revision :>jsonarr string id: the revision unique identifier :>jsonarr boolean merge: whether or not the revision corresponds to a merge commit :>jsonarr string message: the message associated to the revision :>jsonarr 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 :>jsonarr string type: the type of the revision **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options` :statuscode 200: no error :statuscode 404: no revision matching the given criteria could be found in the archive **Example:** .. parsed-literal:: :swh_web_api:`revision/origin/723566/ts/2016-01-17T00:00:00+00:00/log/` """ # noqa result = {} per_page = int(request.query_params.get('per_page', '10')) def lookup_revision_log_by_with_limit(o_id, br, ts, limit=per_page+1): return service.lookup_revision_log_by(o_id, br, ts, limit) error_msg = 'No revision matching origin %s ' % origin_id error_msg += ', branch name %s' % branch_name error_msg += (' and time stamp %s.' % ts) if ts else '.' rev_get = api_lookup( lookup_revision_log_by_with_limit, origin_id, branch_name, ts, notfound_msg=error_msg, enrich_fn=utils.enrich_revision) nb_rev = len(rev_get) if nb_rev == per_page+1: revisions = rev_get[:-1] last_sha1_git = rev_get[-1]['id'] params = {k: v for k, v in {'origin_id': origin_id, 'branch_name': branch_name, 'ts': ts, }.items() if v is not None} query_params = {} query_params['sha1_git'] = last_sha1_git if request.query_params.get('per_page'): query_params['per_page'] = per_page result['headers'] = { 'link-next': reverse('api-revision-origin-log', url_args=params, query_params=query_params) } else: revisions = rev_get result.update({'results': revisions}) return result @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/directory/', 'api-revision-origin-directory') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/directory/(?P<path>.+)/', 'api-revision-origin-directory') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)/directory/', 'api-revision-origin-directory') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)/ts/(?P<ts>.+)/directory/', 'api-revision-origin-directory') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)/directory/(?P<path>.+)/', 'api-revision-origin-directory') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)/ts/(?P<ts>.+)' r'/directory/(?P<path>.+)/', 'api-revision-origin-directory') @api_doc('/revision/origin/directory/', tags=['hidden']) def api_directory_through_revision_origin(request, origin_id, branch_name="refs/heads/master", ts=None, path=None, with_data=False): """ Display directory or content information through a revision identified by origin/branch/timestamp. """ if ts: ts = parse_timestamp(ts) return _revision_directory_by({'origin_id': origin_id, 'branch_name': branch_name, 'ts': ts }, path, request.path, with_data=with_data) @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/', 'api-revision-origin') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)/', 'api-revision-origin') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)' r'/branch/(?P<branch_name>.+)/ts/(?P<ts>.+)/', 'api-revision-origin') @api_route(r'/revision/origin/(?P<origin_id>[0-9]+)/ts/(?P<ts>.+)/', 'api-revision-origin') @api_doc('/revision/origin/') def api_revision_with_origin(request, origin_id, branch_name='HEAD', ts=None): """ .. http:get:: /api/1/revision/origin/(origin_id)/[branch/(branch_name)/][ts/(timestamp)/] Get information about a revision, searching for it based on software origin, branch name, and/or visit timestamp. This endpoint behaves like :http:get:`/api/1/revision/(sha1_git)/`, but operates on the revision that has been found at a given software origin, close to a given point in time, pointed by a given branch. :param int origin_id: a software origin identifier :param string branch_name: optional parameter specifying a fully-qualified branch name associated to the software origin, e.g., "refs/heads/master". Defaults to the HEAD branch. :param string timestamp: optional parameter specifying a timestamp close to which the revision pointed by the given branch should be looked up. The timestamp can be expressed either as an ISO date or as a Unix one (in UTC). Defaults to now. :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 object author: information about the author of the revision :>json string author_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the author of the revision :>json object committer: information about the committer of the revision :>json string committer_url: link to :http:get:`/api/1/person/(person_id)/` to get 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`, :http:method:`options` :statuscode 200: no error :statuscode 404: no revision matching the given criteria could be found in the archive **Example:** .. parsed-literal:: :swh_web_api:`revision/origin/13706355/branch/refs/heads/2.7/` """ # noqa return api_lookup( service.lookup_revision_by, origin_id, branch_name, ts, notfound_msg=('Revision with (origin_id: {}, branch_name: {}' ', ts: {}) not found.'.format(origin_id, branch_name, ts)), enrich_fn=utils.enrich_revision) -@api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/prev/(?P<context>[0-9a-f/]+)/', - 'api-revision-context') -@api_doc('/revision/prev/', tags=['hidden']) -def api_revision_with_context(request, sha1_git, context): - """ - Return information about revision with id sha1_git. - """ - def _enrich_revision(revision, context=context): - return utils.enrich_revision(revision, context) - - return api_lookup( - service.lookup_revision, sha1_git, - notfound_msg='Revision with sha1_git %s not found.' % sha1_git, - enrich_fn=_enrich_revision) - - @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/', 'api-revision') @api_doc('/revision/') def api_revision(request, sha1_git): """ .. 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 object author: information about the author of the revision :>json string author_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the author of the revision :>json object committer: information about the committer of the revision :>json string committer_url: link to :http:get:`/api/1/person/(person_id)/` to get 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`, :http:method:`options` :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 **Example:** .. parsed-literal:: :swh_web_api:`revision/aafb16d69fd30ff58afdd69036a26047f3aebdc6/` """ # noqa return api_lookup( service.lookup_revision, sha1_git, notfound_msg='Revision with sha1_git {} not found.'.format(sha1_git), enrich_fn=utils.enrich_revision) @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/raw/', 'api-revision-raw-message') @api_doc('/revision/raw/', tags=['hidden'], handle_response=True) def api_revision_raw_message(request, sha1_git): """Return the raw data of the message of revision identified by sha1_git """ raw = service.lookup_revision_message(sha1_git) response = HttpResponse(raw['message'], content_type='application/octet-stream') response['Content-disposition'] = \ 'attachment;filename=rev_%s_raw' % sha1_git return response @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/directory/', 'api-revision-directory') @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/directory/(?P<dir_path>.+)/', 'api-revision-directory') @api_doc('/revision/directory/') def api_revision_directory(request, sha1_git, dir_path=None, with_data=False): """ .. http:get:: /api/1/revision/(sha1_git)/directory/[(path)/] Get information about directory (entry) objects associated to revisions. Each revision is associated to a single "root" directory. This endpoint behaves like :http:get:`/api/1/directory/(sha1_git)/[(path)/]`, but operates on the root directory associated to a given revision. :param string sha1_git: hexadecimal representation of the revision **sha1_git** identifier :param string path: optional parameter to get information about the directory entry pointed by that relative path :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 array content: directory entries as returned by :http:get:`/api/1/directory/(sha1_git)/[(path)/]` :>json string path: path of directory from the revision root one :>json string revision: the unique revision identifier :>json string type: the type of the directory **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options` :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 **Example:** .. parsed-literal:: :swh_web_api:`revision/f1b94134a4b879bc55c3dacdb496690c8ebdc03f/directory/` """ # noqa return _revision_directory_by({'sha1_git': sha1_git}, dir_path, request.path, with_data=with_data) @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)/log/', 'api-revision-log') @api_route(r'/revision/(?P<sha1_git>[0-9a-f]+)' r'/prev/(?P<prev_sha1s>[0-9a-f/]+)/log/', 'api-revision-log') @api_doc('/revision/log/') def api_revision_log(request, sha1_git, prev_sha1s=None): """ .. http:get:: /api/1/revision/(sha1_git)[/prev/(prev_sha1s)]/log/ Get a list of all revisions heading to a given one, in other words show the commit log. :param string sha1_git: hexadecimal representation of the revision **sha1_git** identifier :param string prev_sha1s: optional parameter representing the navigation breadcrumbs (descendant revisions previously visited). If multiple values, use / as delimiter. If provided, revisions information will be added at the beginning of the returned list. :query int per_page: number of elements in the returned list, for pagination purpose :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 :resheader Link: indicates that a subsequent result page is available and contains the url pointing to it :>jsonarr object author: information about the author of the revision :>jsonarr string author_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the author of the revision :>jsonarr object committer: information about the committer of the revision :>jsonarr string committer_url: link to :http:get:`/api/1/person/(person_id)/` to get information about the committer of the revision :>jsonarr string committer_date: ISO representation of the commit date (in UTC) :>jsonarr string date: ISO representation of the revision date (in UTC) :>jsonarr string directory: the unique identifier that revision points to :>jsonarr string directory_url: link to :http:get:`/api/1/directory/(sha1_git)/[(path)/]` to get information about the directory associated to the revision :>jsonarr string id: the revision unique identifier :>jsonarr boolean merge: whether or not the revision corresponds to a merge commit :>jsonarr string message: the message associated to the revision :>jsonarr 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 :>jsonarr string type: the type of the revision **Allowed HTTP Methods:** :http:method:`get`, :http:method:`head`, :http:method:`options` :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 **Example:** .. parsed-literal:: :swh_web_api:`revision/e1a315fa3fa734e2a6154ed7b5b9ae0eb8987aad/log/` """ # noqa result = {} per_page = int(request.query_params.get('per_page', '10')) def lookup_revision_log_with_limit(s, limit=per_page+1): return service.lookup_revision_log(s, limit) error_msg = 'Revision with sha1_git %s not found.' % sha1_git rev_get = api_lookup(lookup_revision_log_with_limit, sha1_git, notfound_msg=error_msg, enrich_fn=utils.enrich_revision) nb_rev = len(rev_get) if nb_rev == per_page+1: rev_backward = rev_get[:-1] new_last_sha1 = rev_get[-1]['id'] query_params = {} if request.query_params.get('per_page'): query_params['per_page'] = per_page result['headers'] = { 'link-next': reverse('api-revision-log', url_args={'sha1_git': new_last_sha1}, query_params=query_params) } else: rev_backward = rev_get if not prev_sha1s: # no nav breadcrumbs, so we're done revisions = rev_backward else: rev_forward_ids = prev_sha1s.split('/') rev_forward = api_lookup( service.lookup_revision_multiple, rev_forward_ids, notfound_msg=error_msg, enrich_fn=utils.enrich_revision) revisions = rev_forward + rev_backward result.update({ 'results': revisions }) return result diff --git a/swh/web/tests/api/test_utils.py b/swh/web/tests/api/test_utils.py index 5e5725702..de0855e82 100644 --- a/swh/web/tests/api/test_utils.py +++ b/swh/web/tests/api/test_utils.py @@ -1,688 +1,599 @@ # Copyright (C) 2015-2018 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 from unittest.mock import patch, call from swh.web.api import utils from swh.web.tests.testcase import WebTestCase class UtilsTestCase(WebTestCase): def setUp(self): self.maxDiff = None self.url_map = [dict(rule='/other/<slug>', methods=set(['GET', 'POST', 'HEAD']), endpoint='foo'), dict(rule='/some/old/url/<slug>', methods=set(['GET', 'POST']), endpoint='blablafn'), dict(rule='/other/old/url/<int:id>', methods=set(['GET', 'HEAD']), endpoint='bar'), dict(rule='/other', methods=set([]), endpoint=None), dict(rule='/other2', methods=set([]), endpoint=None)] self.sample_content_hashes = { 'blake2s256': ('791e07fcea240ade6dccd0a9309141673' 'c31242cae9c237cf3855e151abc78e9'), 'sha1': 'dc2830a9e72f23c1dfebef4413003221baa5fb62', 'sha1_git': 'fe95a46679d128ff167b7c55df5d02356c5a1ae1', 'sha256': ('b5c7fe0536f44ef60c8780b6065d30bca74a5cd06' 'd78a4a71ba1ad064770f0c9') } def test_filter_field_keys_dict_unknown_keys(self): # when actual_res = utils.filter_field_keys( {'directory': 1, 'file': 2, 'link': 3}, {'directory1', 'file2'}) # then self.assertEqual(actual_res, {}) def test_filter_field_keys_dict(self): # when actual_res = utils.filter_field_keys( {'directory': 1, 'file': 2, 'link': 3}, {'directory', 'link'}) # then self.assertEqual(actual_res, {'directory': 1, 'link': 3}) def test_filter_field_keys_list_unknown_keys(self): # when actual_res = utils.filter_field_keys( [{'directory': 1, 'file': 2, 'link': 3}, {'1': 1, '2': 2, 'link': 3}], {'d'}) # then self.assertEqual(actual_res, [{}, {}]) def test_filter_field_keys_map(self): # when actual_res = utils.filter_field_keys( map(lambda x: {'i': x['i']+1, 'j': x['j']}, [{'i': 1, 'j': None}, {'i': 2, 'j': None}, {'i': 3, 'j': None}]), {'i'}) # then self.assertEqual(list(actual_res), [{'i': 2}, {'i': 3}, {'i': 4}]) def test_filter_field_keys_list(self): # when actual_res = utils.filter_field_keys( [{'directory': 1, 'file': 2, 'link': 3}, {'dir': 1, 'fil': 2, 'lin': 3}], {'directory', 'dir'}) # then self.assertEqual(actual_res, [{'directory': 1}, {'dir': 1}]) def test_filter_field_keys_other(self): # given input_set = {1, 2} # when actual_res = utils.filter_field_keys(input_set, {'a', '1'}) # then self.assertEqual(actual_res, input_set) def test_person_to_string(self): self.assertEqual(utils.person_to_string(dict(name='raboof', email='foo@bar')), 'raboof <foo@bar>') def test_enrich_release_0(self): # when actual_release = utils.enrich_release({}) # then self.assertEqual(actual_release, {}) @patch('swh.web.api.utils.reverse') def test_enrich_release_1(self, mock_django_reverse): # given def reverse_test_context(view_name, url_args): if view_name == 'api-content': id = url_args['q'] return '/api/1/content/%s/' % id elif view_name == 'api-person': id = url_args['person_id'] return '/api/1/person/%s/' % id else: raise ValueError( 'This should not happened so fail if it does.') mock_django_reverse.side_effect = reverse_test_context # when actual_release = utils.enrich_release({ 'target': '123', 'target_type': 'content', 'author': { 'id': 100, 'name': 'author release name', 'email': 'author@email', }, }) # then self.assertEqual(actual_release, { 'target': '123', 'target_type': 'content', 'target_url': '/api/1/content/sha1_git:123/', 'author_url': '/api/1/person/100/', 'author': { 'id': 100, 'name': 'author release name', 'email': 'author@email', }, }) mock_django_reverse.assert_has_calls([ call('api-content', url_args={'q': 'sha1_git:123'}), call('api-person', url_args={'person_id': 100}) ]) @patch('swh.web.api.utils.reverse') def test_enrich_release_2(self, mock_django_reverse): # given mock_django_reverse.return_value = '/api/1/dir/23/' # when actual_release = utils.enrich_release({'target': '23', 'target_type': 'directory'}) # then self.assertEqual(actual_release, { 'target': '23', 'target_type': 'directory', 'target_url': '/api/1/dir/23/' }) mock_django_reverse.assert_called_once_with('api-directory', url_args={'sha1_git': '23'}) # noqa @patch('swh.web.api.utils.reverse') def test_enrich_release_3(self, mock_django_reverse): # given mock_django_reverse.return_value = '/api/1/rev/3/' # when actual_release = utils.enrich_release({'target': '3', 'target_type': 'revision'}) # then self.assertEqual(actual_release, { 'target': '3', 'target_type': 'revision', 'target_url': '/api/1/rev/3/' }) mock_django_reverse.assert_called_once_with('api-revision', url_args={'sha1_git': '3'}) @patch('swh.web.api.utils.reverse') def test_enrich_release_4(self, mock_django_reverse): # given mock_django_reverse.return_value = '/api/1/rev/4/' # when actual_release = utils.enrich_release({'target': '4', 'target_type': 'release'}) # then self.assertEqual(actual_release, { 'target': '4', 'target_type': 'release', 'target_url': '/api/1/rev/4/' }) mock_django_reverse.assert_called_once_with('api-release', url_args={'sha1_git': '4'}) @patch('swh.web.api.utils.reverse') def test_enrich_directory_no_type(self, mock_django_reverse): # when/then self.assertEqual(utils.enrich_directory({'id': 'dir-id'}), {'id': 'dir-id'}) # given mock_django_reverse.return_value = '/api/content/sha1_git:123/' # when actual_directory = utils.enrich_directory({ 'id': 'dir-id', 'type': 'file', 'target': '123', }) # then self.assertEqual(actual_directory, { 'id': 'dir-id', 'type': 'file', 'target': '123', 'target_url': '/api/content/sha1_git:123/', }) mock_django_reverse.assert_called_once_with( 'api-content', url_args={'q': 'sha1_git:123'}) @patch('swh.web.api.utils.reverse') def test_enrich_directory_with_context_and_type_file( self, mock_django_reverse, ): # given mock_django_reverse.return_value = '/api/content/sha1_git:123/' # when actual_directory = utils.enrich_directory({ 'id': 'dir-id', 'type': 'file', 'name': 'hy', 'target': '789', }, context_url='/api/revision/revsha1/directory/prefix/path/') # then self.assertEqual(actual_directory, { 'id': 'dir-id', 'type': 'file', 'name': 'hy', 'target': '789', 'target_url': '/api/content/sha1_git:123/', 'file_url': '/api/revision/revsha1/directory' '/prefix/path/hy/' }) mock_django_reverse.assert_called_once_with( 'api-content', url_args={'q': 'sha1_git:789'}) @patch('swh.web.api.utils.reverse') def test_enrich_directory_with_context_and_type_dir( self, mock_django_reverse, ): # given mock_django_reverse.return_value = '/api/directory/456/' # when actual_directory = utils.enrich_directory({ 'id': 'dir-id', 'type': 'dir', 'name': 'emacs-42', 'target_type': 'file', 'target': '456', }, context_url='/api/revision/origin/2/directory/some/prefix/path/') # then self.assertEqual(actual_directory, { 'id': 'dir-id', 'type': 'dir', 'target_type': 'file', 'name': 'emacs-42', 'target': '456', 'target_url': '/api/directory/456/', 'dir_url': '/api/revision/origin/2/directory' '/some/prefix/path/emacs-42/' }) mock_django_reverse.assert_called_once_with('api-directory', url_args={'sha1_git': '456'}) # noqa def test_enrich_content_without_hashes(self): # when/then self.assertEqual(utils.enrich_content({'id': '123'}), {'id': '123'}) @patch('swh.web.api.utils.reverse') def test_enrich_content_with_hashes(self, mock_django_reverse): for algo, hash in self.sample_content_hashes.items(): query_string = '%s:%s' % (algo, hash) # given mock_django_reverse.side_effect = [ '/api/content/%s/raw/' % query_string, '/api/filetype/%s/' % query_string, '/api/language/%s/' % query_string, '/api/license/%s/' % query_string ] # when enriched_content = utils.enrich_content( { algo: hash, }, query_string=query_string ) # then self.assertEqual( enriched_content, { algo: hash, 'data_url': '/api/content/%s/raw/' % query_string, 'filetype_url': '/api/filetype/%s/' % query_string, 'language_url': '/api/language/%s/' % query_string, 'license_url': '/api/license/%s/' % query_string, } ) mock_django_reverse.assert_has_calls([ call('api-content-raw', url_args={'q': query_string}), call('api-content-filetype', url_args={'q': query_string}), call('api-content-language', url_args={'q': query_string}), call('api-content-license', url_args={'q': query_string}), ]) mock_django_reverse.reset() @patch('swh.web.api.utils.reverse') def test_enrich_content_with_hashes_and_top_level_url(self, mock_django_reverse): for algo, hash in self.sample_content_hashes.items(): query_string = '%s:%s' % (algo, hash) # given mock_django_reverse.side_effect = [ '/api/content/%s/' % query_string, '/api/content/%s/raw/' % query_string, '/api/filetype/%s/' % query_string, '/api/language/%s/' % query_string, '/api/license/%s/' % query_string, ] # when enriched_content = utils.enrich_content( { algo: hash }, top_url=True, query_string=query_string ) # then self.assertEqual( enriched_content, { algo: hash, 'content_url': '/api/content/%s/' % query_string, 'data_url': '/api/content/%s/raw/' % query_string, 'filetype_url': '/api/filetype/%s/' % query_string, 'language_url': '/api/language/%s/' % query_string, 'license_url': '/api/license/%s/' % query_string, } ) mock_django_reverse.assert_has_calls([ call('api-content', url_args={'q': query_string}), call('api-content-raw', url_args={'q': query_string}), call('api-content-filetype', url_args={'q': query_string}), call('api-content-language', url_args={'q': query_string}), call('api-content-license', url_args={'q': query_string}), ]) mock_django_reverse.reset() def _reverse_context_test(self, view_name, url_args): if view_name == 'api-revision': return '/api/revision/%s/' % url_args['sha1_git'] elif view_name == 'api-revision-context': return '/api/revision/%s/prev/%s/' % (url_args['sha1_git'], url_args['context']) # noqa elif view_name == 'api-revision-log': if 'prev_sha1s' in url_args: return '/api/revision/%s/prev/%s/log/' % (url_args['sha1_git'], url_args['prev_sha1s']) # noqa else: return '/api/revision/%s/log/' % url_args['sha1_git'] @patch('swh.web.api.utils.reverse') def test_enrich_revision_without_children_or_parent( self, mock_django_reverse, ): # given def reverse_test(view_name, url_args): if view_name == 'api-revision': return '/api/revision/' + url_args['sha1_git'] + '/' elif view_name == 'api-revision-log': return '/api/revision/' + url_args['sha1_git'] + '/log/' elif view_name == 'api-directory': return '/api/directory/' + url_args['sha1_git'] + '/' elif view_name == 'api-person': return '/api/person/' + url_args['person_id'] + '/' mock_django_reverse.side_effect = reverse_test # when actual_revision = utils.enrich_revision({ 'id': 'rev-id', 'directory': '123', 'author': {'id': '1'}, 'committer': {'id': '2'}, }) expected_revision = { 'id': 'rev-id', 'directory': '123', 'url': '/api/revision/rev-id/', 'history_url': '/api/revision/rev-id/log/', 'directory_url': '/api/directory/123/', 'author': {'id': '1'}, 'author_url': '/api/person/1/', 'committer': {'id': '2'}, 'committer_url': '/api/person/2/' } # then self.assertEqual(actual_revision, expected_revision) mock_django_reverse.assert_has_calls( [call('api-revision', url_args={'sha1_git': 'rev-id'}), call('api-revision-log', url_args={'sha1_git': 'rev-id'}), call('api-person', url_args={'person_id': '1'}), call('api-person', url_args={'person_id': '2'}), call('api-directory', url_args={'sha1_git': '123'})]) @patch('swh.web.api.utils.reverse') def test_enrich_revision_with_children_and_parent_no_dir( self, mock_django_reverse, ): # given mock_django_reverse.side_effect = self._reverse_context_test # when actual_revision = utils.enrich_revision({ 'id': 'rev-id', 'parents': ['123'], 'children': ['456'], - }, context='prev-rev') + }) expected_revision = { 'id': 'rev-id', 'url': '/api/revision/rev-id/', 'history_url': '/api/revision/rev-id/log/', - 'history_context_url': '/api/revision/rev-id/prev/prev-rev/log/', 'parents': [{'id': '123', 'url': '/api/revision/123/'}], 'children': ['456'], - 'children_urls': ['/api/revision/456/', - '/api/revision/prev-rev/'], + 'children_urls': ['/api/revision/456/'], } # then self.assertEqual(actual_revision, expected_revision) mock_django_reverse.assert_has_calls( - [call('api-revision', url_args={'sha1_git': 'prev-rev'}), - call('api-revision', url_args={'sha1_git': 'rev-id'}), + [call('api-revision', url_args={'sha1_git': 'rev-id'}), call('api-revision-log', url_args={'sha1_git': 'rev-id'}), - call('api-revision-log', url_args={'sha1_git': 'rev-id', - 'prev_sha1s': 'prev-rev'}), call('api-revision', url_args={'sha1_git': '123'}), call('api-revision', url_args={'sha1_git': '456'})]) @patch('swh.web.api.utils.reverse') def test_enrich_revision_no_context(self, mock_django_reverse): # given mock_django_reverse.side_effect = self._reverse_context_test # when actual_revision = utils.enrich_revision({ 'id': 'rev-id', 'parents': ['123'], 'children': ['456'], }) expected_revision = { 'id': 'rev-id', 'url': '/api/revision/rev-id/', 'history_url': '/api/revision/rev-id/log/', 'parents': [{'id': '123', 'url': '/api/revision/123/'}], 'children': ['456'], 'children_urls': ['/api/revision/456/'] } # then self.assertEqual(actual_revision, expected_revision) mock_django_reverse.assert_has_calls( [call('api-revision', url_args={'sha1_git': 'rev-id'}), call('api-revision-log', url_args={'sha1_git': 'rev-id'}), call('api-revision', url_args={'sha1_git': '123'}), call('api-revision', url_args={'sha1_git': '456'})]) - @patch('swh.web.api.utils.reverse') - def test_enrich_revision_context_empty_prev_list( - self, mock_django_reverse, - ): - # given - mock_django_reverse.side_effect = self._reverse_context_test - - # when - expected_revision = { - 'id': 'rev-id', - 'url': '/api/revision/rev-id/', - 'history_url': '/api/revision/rev-id/log/', - 'history_context_url': ('/api/revision/rev-id/' - 'prev/prev-rev/log/'), - 'parents': [{'id': '123', 'url': '/api/revision/123/'}], - 'children': ['456'], - 'children_urls': ['/api/revision/456/', - '/api/revision/prev-rev/'], - } - - actual_revision = utils.enrich_revision({ - 'id': 'rev-id', - 'url': '/api/revision/rev-id/', - 'parents': ['123'], - 'children': ['456']}, context='prev-rev') - - # then - self.assertEqual(actual_revision, expected_revision) - mock_django_reverse.assert_has_calls( - [call('api-revision', url_args={'sha1_git': 'prev-rev'}), - call('api-revision', url_args={'sha1_git': 'rev-id'}), - call('api-revision-log', url_args={'sha1_git': 'rev-id'}), - call('api-revision-log', url_args={'sha1_git': 'rev-id', - 'prev_sha1s': 'prev-rev'}), - call('api-revision', url_args={'sha1_git': '123'}), - call('api-revision', url_args={'sha1_git': '456'})]) - - @patch('swh.web.api.utils.reverse') - def test_enrich_revision_context_some_prev_list(self, mock_django_reverse): - # given - mock_django_reverse.side_effect = self._reverse_context_test - - # when - expected_revision = { - 'id': 'rev-id', - 'url': '/api/revision/rev-id/', - 'history_url': '/api/revision/rev-id/log/', - 'history_context_url': ('/api/revision/rev-id/' - 'prev/prev1-rev/prev0-rev/log/'), - 'parents': [{'id': '123', 'url': '/api/revision/123/'}], - 'children': ['456'], - 'children_urls': ['/api/revision/456/', - '/api/revision/prev0-rev/prev/prev1-rev/'], - } - - actual_revision = utils.enrich_revision({ - 'id': 'rev-id', - 'parents': ['123'], - 'children': ['456']}, context='prev1-rev/prev0-rev') - - # then - self.assertEqual(actual_revision, expected_revision) - mock_django_reverse.assert_has_calls( - [call('api-revision-context', url_args={'context': 'prev1-rev', - 'sha1_git': 'prev0-rev'}), - call('api-revision', url_args={'sha1_git': 'rev-id'}), - call('api-revision-log', url_args={'sha1_git': 'rev-id'}), - call('api-revision-log', url_args={'prev_sha1s': 'prev1-rev/prev0-rev', # noqa - 'sha1_git': 'rev-id'}), - call('api-revision', url_args={'sha1_git': '123'}), - call('api-revision', url_args={'sha1_git': '456'})]) - def _reverse_rev_message_test(self, view_name, url_args): if view_name == 'api-revision': return '/api/revision/%s/' % url_args['sha1_git'] elif view_name == 'api-revision-log': if 'prev_sha1s' in url_args and url_args['prev_sha1s'] is not None: return '/api/revision/%s/prev/%s/log/' % (url_args['sha1_git'], url_args['prev_sha1s']) # noqa else: return '/api/revision/%s/log/' % url_args['sha1_git'] elif view_name == 'api-revision-raw-message': return '/api/revision/' + url_args['sha1_git'] + '/raw/' else: return '/api/revision/%s/prev/%s/' % (url_args['sha1_git'], url_args['context']) # noqa @patch('swh.web.api.utils.reverse') def test_enrich_revision_with_no_message(self, mock_django_reverse): # given mock_django_reverse.side_effect = self._reverse_rev_message_test # when expected_revision = { 'id': 'rev-id', 'url': '/api/revision/rev-id/', 'history_url': '/api/revision/rev-id/log/', - 'history_context_url': ('/api/revision/rev-id/' - 'prev/prev-rev/log/'), 'message': None, 'parents': [{'id': '123', 'url': '/api/revision/123/'}], 'children': ['456'], - 'children_urls': ['/api/revision/456/', - '/api/revision/prev-rev/'], + 'children_urls': ['/api/revision/456/'], } actual_revision = utils.enrich_revision({ 'id': 'rev-id', 'message': None, 'parents': ['123'], 'children': ['456'], - }, context='prev-rev') + }) # then self.assertEqual(actual_revision, expected_revision) mock_django_reverse.assert_has_calls( - [call('api-revision', url_args={'sha1_git': 'prev-rev'}), - call('api-revision', url_args={'sha1_git': 'rev-id'}), + [call('api-revision', url_args={'sha1_git': 'rev-id'}), call('api-revision-log', url_args={'sha1_git': 'rev-id'}), - call('api-revision-log', url_args={'sha1_git': 'rev-id', - 'prev_sha1s': 'prev-rev'}), call('api-revision', url_args={'sha1_git': '123'}), call('api-revision', url_args={'sha1_git': '456'})] ) @patch('swh.web.api.utils.reverse') def test_enrich_revision_with_invalid_message(self, mock_django_reverse): # given mock_django_reverse.side_effect = self._reverse_rev_message_test # when actual_revision = utils.enrich_revision({ 'id': 'rev-id', 'message': None, 'message_decoding_failed': True, 'parents': ['123'], 'children': ['456'], - }, context='prev-rev') + }) expected_revision = { 'id': 'rev-id', 'url': '/api/revision/rev-id/', 'history_url': '/api/revision/rev-id/log/', - 'history_context_url': ('/api/revision/rev-id/' - 'prev/prev-rev/log/'), 'message': None, 'message_decoding_failed': True, 'message_url': '/api/revision/rev-id/raw/', 'parents': [{'id': '123', 'url': '/api/revision/123/'}], 'children': ['456'], - 'children_urls': ['/api/revision/456/', - '/api/revision/prev-rev/'], + 'children_urls': ['/api/revision/456/'], } # then self.assertEqual(actual_revision, expected_revision) mock_django_reverse.assert_has_calls( - [call('api-revision', url_args={'sha1_git': 'prev-rev'}), - call('api-revision', url_args={'sha1_git': 'rev-id'}), + [call('api-revision', url_args={'sha1_git': 'rev-id'}), call('api-revision-log', url_args={'sha1_git': 'rev-id'}), - call('api-revision-log', url_args={'sha1_git': 'rev-id', - 'prev_sha1s': 'prev-rev'}), call('api-revision', url_args={'sha1_git': '123'}), call('api-revision', url_args={'sha1_git': '456'}), call('api-revision-raw-message', url_args={'sha1_git': 'rev-id'})]) # noqa diff --git a/swh/web/tests/api/views/test_revision.py b/swh/web/tests/api/views/test_revision.py index ed583251c..e4a7fa847 100644 --- a/swh/web/tests/api/views/test_revision.py +++ b/swh/web/tests/api/views/test_revision.py @@ -1,883 +1,847 @@ # Copyright (C) 2015-2018 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 from rest_framework.test import APITestCase from unittest.mock import patch from swh.web.common.exc import NotFoundExc from swh.web.api.views.revision import ( _revision_directory_by ) from swh.web.tests.testcase import WebTestCase class RevisionApiTestCase(WebTestCase, APITestCase): @patch('swh.web.api.views.revision.service') def test_api_revision(self, mock_service): # given stub_revision = { 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': ['8734ef7e7c357ce2af928115c6c6a42b7e2a44e7'], 'type': 'tar', 'synthetic': True, 'metadata': { 'original_artifact': [{ 'archive_type': 'tar', 'name': 'webbase-5.7.0.tar.gz', 'sha1': '147f73f369733d088b7a6fa9c4e0273dcd3c7ccd', 'sha1_git': '6a15ea8b881069adedf11feceec35588f2cfe8f1', 'sha256': '401d0df797110bea805d358b85bcc1ced29549d3d73f' '309d36484e7edf7bb912' }] }, } mock_service.lookup_revision.return_value = stub_revision expected_revision = { 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'url': '/api/1/revision/18d8be353ed3480476f032475e7c233eff7371d5/', 'history_url': '/api/1/revision/18d8be353ed3480476f032475e7c233e' 'ff7371d5/log/', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'directory_url': '/api/1/directory/7834ef7e7c357ce2af928115c6c6' 'a42b7e2a44e6/', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': [{ 'id': '8734ef7e7c357ce2af928115c6c6a42b7e2a44e7', 'url': '/api/1/revision/8734ef7e7c357ce2af928115c6c6a42b7e2a44e7/' # noqa }], 'type': 'tar', 'synthetic': True, 'metadata': { 'original_artifact': [{ 'archive_type': 'tar', 'name': 'webbase-5.7.0.tar.gz', 'sha1': '147f73f369733d088b7a6fa9c4e0273dcd3c7ccd', 'sha1_git': '6a15ea8b881069adedf11feceec35588f2cfe8f1', 'sha256': '401d0df797110bea805d358b85bcc1ced29549d3d73f' '309d36484e7edf7bb912' }] }, } # when rv = self.client.get('/api/1/revision/' '18d8be353ed3480476f032475e7c233eff7371d5/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(expected_revision, rv.data) mock_service.lookup_revision.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.revision.service') def test_api_revision_not_found(self, mock_service): # given mock_service.lookup_revision.return_value = None # when rv = self.client.get('/api/1/revision/12345/') # then self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Revision with sha1_git 12345 not found.'}) @patch('swh.web.api.views.revision.service') def test_api_revision_raw_ok(self, mock_service): # given stub_revision = {'message': 'synthetic revision message'} mock_service.lookup_revision_message.return_value = stub_revision # when rv = self.client.get('/api/1/revision/18d8be353ed3480476f032475e7c2' '33eff7371d5/raw/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/octet-stream') self.assertEqual(rv.content, b'synthetic revision message') mock_service.lookup_revision_message.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.revision.service') def test_api_revision_raw_ok_no_msg(self, mock_service): # given mock_service.lookup_revision_message.side_effect = NotFoundExc( 'No message for revision') # when rv = self.client.get('/api/1/revision/' '18d8be353ed3480476f032475e7c233eff7371d5/raw/') # then self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No message for revision'}) self.assertEqual mock_service.lookup_revision_message.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.revision.service') def test_api_revision_raw_ko_no_rev(self, mock_service): # given mock_service.lookup_revision_message.side_effect = NotFoundExc( 'No revision found') # when rv = self.client.get('/api/1/revision/' '18d8be353ed3480476f032475e7c233eff7371d5/raw/') # then self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No revision found'}) mock_service.lookup_revision_message.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.revision.service') def test_api_revision_with_origin_not_found(self, mock_service): mock_service.lookup_revision_by.return_value = None rv = self.client.get('/api/1/revision/origin/123/') # then self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertIn('Revision with (origin_id: 123', rv.data['reason']) self.assertIn('not found', rv.data['reason']) self.assertEqual('NotFoundExc', rv.data['exception']) mock_service.lookup_revision_by.assert_called_once_with( '123', 'HEAD', None) @patch('swh.web.api.views.revision.service') def test_api_revision_with_origin(self, mock_service): mock_revision = { 'id': '32', 'directory': '21', 'message': 'message 1', 'type': 'deb', } expected_revision = { 'id': '32', 'url': '/api/1/revision/32/', 'history_url': '/api/1/revision/32/log/', 'directory': '21', 'directory_url': '/api/1/directory/21/', 'message': 'message 1', 'type': 'deb', } mock_service.lookup_revision_by.return_value = mock_revision rv = self.client.get('/api/1/revision/origin/1/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revision) mock_service.lookup_revision_by.assert_called_once_with( '1', 'HEAD', None) @patch('swh.web.api.views.revision.service') def test_api_revision_with_origin_and_branch_name(self, mock_service): mock_revision = { 'id': '12', 'directory': '23', 'message': 'message 2', 'type': 'tar', } mock_service.lookup_revision_by.return_value = mock_revision expected_revision = { 'id': '12', 'url': '/api/1/revision/12/', 'history_url': '/api/1/revision/12/log/', 'directory': '23', 'directory_url': '/api/1/directory/23/', 'message': 'message 2', 'type': 'tar', } rv = self.client.get('/api/1/revision/origin/1' '/branch/refs/origin/dev/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revision) mock_service.lookup_revision_by.assert_called_once_with( '1', 'refs/origin/dev', None) @patch('swh.web.api.views.revision.service') @patch('swh.web.api.views.revision.utils') def test_api_revision_with_origin_and_branch_name_and_timestamp(self, mock_utils, mock_service): # noqa mock_revision = { 'id': '123', 'directory': '456', 'message': 'message 3', 'type': 'tar', } mock_service.lookup_revision_by.return_value = mock_revision expected_revision = { 'id': '123', 'url': '/api/1/revision/123/', 'history_url': '/api/1/revision/123/log/', 'directory': '456', 'directory_url': '/api/1/directory/456/', 'message': 'message 3', 'type': 'tar', } mock_utils.enrich_revision.return_value = expected_revision rv = self.client.get('/api/1/revision' '/origin/1' '/branch/refs/origin/dev' '/ts/1452591542/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revision) mock_service.lookup_revision_by.assert_called_once_with( '1', 'refs/origin/dev', '1452591542') mock_utils.enrich_revision.assert_called_once_with( mock_revision) @patch('swh.web.api.views.revision.service') @patch('swh.web.api.views.revision.utils') def test_api_revision_with_origin_and_branch_name_and_timestamp_escapes( self, mock_utils, mock_service): mock_revision = { 'id': '999', } mock_service.lookup_revision_by.return_value = mock_revision expected_revision = { 'id': '999', 'url': '/api/1/revision/999/', 'history_url': '/api/1/revision/999/log/', } mock_utils.enrich_revision.return_value = expected_revision rv = self.client.get('/api/1/revision' '/origin/1' '/branch/refs%2Forigin%2Fdev' '/ts/Today%20is%20' 'January%201,%202047%20at%208:21:00AM/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revision) mock_service.lookup_revision_by.assert_called_once_with( '1', 'refs/origin/dev', 'Today is January 1, 2047 at 8:21:00AM') mock_utils.enrich_revision.assert_called_once_with( mock_revision) @patch('swh.web.api.views.revision.service') def test_revision_directory_by_ko_raise(self, mock_service): # given mock_service.lookup_directory_through_revision.side_effect = NotFoundExc('not') # noqa # when with self.assertRaises(NotFoundExc): _revision_directory_by( {'sha1_git': 'id'}, None, '/api/1/revision/sha1/directory/') # then mock_service.lookup_directory_through_revision.assert_called_once_with( {'sha1_git': 'id'}, None, limit=100, with_data=False) @patch('swh.web.api.views.revision.service') def test_revision_directory_by_type_dir(self, mock_service): # given mock_service.lookup_directory_through_revision.return_value = ( 'rev-id', { 'type': 'dir', 'revision': 'rev-id', 'path': 'some/path', 'content': [] }) # when actual_dir_content = _revision_directory_by( {'sha1_git': 'blah-id'}, 'some/path', '/api/1/revision/sha1/directory/') # then self.assertEqual(actual_dir_content, { 'type': 'dir', 'revision': 'rev-id', 'path': 'some/path', 'content': [] }) mock_service.lookup_directory_through_revision.assert_called_once_with( {'sha1_git': 'blah-id'}, 'some/path', limit=100, with_data=False) @patch('swh.web.api.views.revision.service') def test_revision_directory_by_type_file(self, mock_service): # given mock_service.lookup_directory_through_revision.return_value = ( 'rev-id', { 'type': 'file', 'revision': 'rev-id', 'path': 'some/path', 'content': {'blah': 'blah'} }) # when actual_dir_content = _revision_directory_by( {'sha1_git': 'sha1'}, 'some/path', '/api/1/revision/origin/2/directory/', limit=1000, with_data=True) # then self.assertEqual(actual_dir_content, { 'type': 'file', 'revision': 'rev-id', 'path': 'some/path', 'content': {'blah': 'blah'} }) mock_service.lookup_directory_through_revision.assert_called_once_with( {'sha1_git': 'sha1'}, 'some/path', limit=1000, with_data=True) @patch('swh.web.api.views.revision.parse_timestamp') @patch('swh.web.api.views.revision._revision_directory_by') @patch('swh.web.api.views.revision.utils') def test_api_directory_through_revision_origin_ko_not_found(self, mock_utils, mock_rev_dir, mock_parse_timestamp): # noqa mock_rev_dir.side_effect = NotFoundExc('not found') mock_parse_timestamp.return_value = '2012-10-20 00:00:00' rv = self.client.get('/api/1/revision' '/origin/10' '/branch/refs/remote/origin/dev' '/ts/2012-10-20' '/directory/') # then self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, { 'exception': 'NotFoundExc', 'reason': 'not found'}) mock_rev_dir.assert_called_once_with( {'origin_id': '10', 'branch_name': 'refs/remote/origin/dev', 'ts': '2012-10-20 00:00:00'}, None, '/api/1/revision' '/origin/10' '/branch/refs/remote/origin/dev' '/ts/2012-10-20' '/directory/', with_data=False) @patch('swh.web.api.views.revision._revision_directory_by') def test_api_directory_through_revision_origin(self, mock_revision_dir): expected_res = [{ 'id': '123' }] mock_revision_dir.return_value = expected_res rv = self.client.get('/api/1/revision/origin/3/directory/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_res) mock_revision_dir.assert_called_once_with({ 'origin_id': '3', 'branch_name': 'refs/heads/master', 'ts': None}, None, '/api/1/revision/origin/3/directory/', with_data=False) @patch('swh.web.api.views.revision.service') def test_api_revision_log(self, mock_service): # given stub_revisions = [{ 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': ['7834ef7e7c357ce2af928115c6c6a42b7e2a4345'], 'type': 'tar', 'synthetic': True, }] mock_service.lookup_revision_log.return_value = stub_revisions expected_revisions = [{ 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'url': '/api/1/revision/18d8be353ed3480476f032475e7c233eff7371d5/', 'history_url': '/api/1/revision/18d8be353ed3480476f032475e7c233ef' 'f7371d5/log/', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'directory_url': '/api/1/directory/7834ef7e7c357ce2af928115c6c6a' '42b7e2a44e6/', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': [{ 'id': '7834ef7e7c357ce2af928115c6c6a42b7e2a4345', 'url': '/api/1/revision/7834ef7e7c357ce2af928115c6c6a42b7e2a4345/', # noqa }], 'type': 'tar', 'synthetic': True, }] # when rv = self.client.get('/api/1/revision/8834ef7e7c357ce2af928115c6c6a42' 'b7e2a44e6/log/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revisions) self.assertFalse(rv.has_header('Link')) mock_service.lookup_revision_log.assert_called_once_with( '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 11) @patch('swh.web.api.views.revision.service') def test_api_revision_log_with_next(self, mock_service): # given stub_revisions = [] for i in range(27): stub_revisions.append({'id': str(i)}) mock_service.lookup_revision_log.return_value = stub_revisions[:26] expected_revisions = [x for x in stub_revisions if int(x['id']) < 25] for e in expected_revisions: e['url'] = '/api/1/revision/%s/' % e['id'] e['history_url'] = '/api/1/revision/%s/log/' % e['id'] # when rv = self.client.get('/api/1/revision/8834ef7e7c357ce2af928115c6c6a42' 'b7e2a44e6/log/?per_page=25') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revisions) self.assertEqual(rv['Link'], '</api/1/revision/25/log/?per_page=25>; rel="next"') mock_service.lookup_revision_log.assert_called_once_with( '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 26) @patch('swh.web.api.views.revision.service') def test_api_revision_log_not_found(self, mock_service): # given mock_service.lookup_revision_log.return_value = None # when rv = self.client.get('/api/1/revision/8834ef7e7c357ce2af928115c6c6' 'a42b7e2a44e6/log/') # then self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Revision with sha1_git' ' 8834ef7e7c357ce2af928115c6c6a42b7e2a44e6 not found.'}) self.assertFalse(rv.has_header('Link')) mock_service.lookup_revision_log.assert_called_once_with( '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 11) @patch('swh.web.api.views.revision.service') def test_api_revision_log_context(self, mock_service): # given stub_revisions = [{ 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': ['7834ef7e7c357ce2af928115c6c6a42b7e2a4345'], 'type': 'tar', 'synthetic': True, }] mock_service.lookup_revision_log.return_value = stub_revisions mock_service.lookup_revision_multiple.return_value = [{ 'id': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'directory': '18d8be353ed3480476f032475e7c233eff7371d5', 'author_name': 'Name Surname', 'author_email': 'name@surname.com', 'committer_name': 'Name Surname', 'committer_email': 'name@surname.com', 'message': 'amazing revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': ['adc83b19e793491b1c6ea0fd8b46cd9f32e592fc'], 'type': 'tar', 'synthetic': True, }] expected_revisions = [ { 'url': '/api/1/revision/' '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6/', 'history_url': '/api/1/revision/' '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6/log/', 'id': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'directory': '18d8be353ed3480476f032475e7c233eff7371d5', 'directory_url': '/api/1/directory/' '18d8be353ed3480476f032475e7c233eff7371d5/', 'author_name': 'Name Surname', 'author_email': 'name@surname.com', 'committer_name': 'Name Surname', 'committer_email': 'name@surname.com', 'message': 'amazing revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': [{ 'id': 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc', 'url': '/api/1/revision/adc83b19e793491b1c6ea0fd8b46cd9f32e592fc/', # noqa }], 'type': 'tar', 'synthetic': True, }, { 'url': '/api/1/revision/' '18d8be353ed3480476f032475e7c233eff7371d5/', 'history_url': '/api/1/revision/' '18d8be353ed3480476f032475e7c233eff7371d5/log/', 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'directory_url': '/api/1/directory/' '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6/', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': [{ 'id': '7834ef7e7c357ce2af928115c6c6a42b7e2a4345', 'url': '/api/1/revision/7834ef7e7c357ce2af928115c6c6a42b7e2a4345/', # noqa }], 'type': 'tar', 'synthetic': True, }] # when rv = self.client.get('/api/1/revision/18d8be353ed3480476f0' '32475e7c233eff7371d5/prev/21145781e2' '6ad1f978e/log/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(expected_revisions, rv.data) self.assertFalse(rv.has_header('Link')) mock_service.lookup_revision_log.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5', 11) mock_service.lookup_revision_multiple.assert_called_once_with( ['21145781e26ad1f978e']) @patch('swh.web.api.views.revision.service') def test_api_revision_log_by(self, mock_service): # given stub_revisions = [{ 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': ['7834ef7e7c357ce2af928115c6c6a42b7e2a4345'], 'type': 'tar', 'synthetic': True, }] mock_service.lookup_revision_log_by.return_value = stub_revisions expected_revisions = [{ 'id': '18d8be353ed3480476f032475e7c233eff7371d5', 'url': '/api/1/revision/18d8be353ed3480476f032475e7c233eff7371d5/', 'history_url': '/api/1/revision/18d8be353ed3480476f032475e7c233ef' 'f7371d5/log/', 'directory': '7834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 'directory_url': '/api/1/directory/7834ef7e7c357ce2af928115c6c6a' '42b7e2a44e6/', 'author_name': 'Software Heritage', 'author_email': 'robot@softwareheritage.org', 'committer_name': 'Software Heritage', 'committer_email': 'robot@softwareheritage.org', 'message': 'synthetic revision message', 'date_offset': 0, 'committer_date_offset': 0, 'parents': [{ 'id': '7834ef7e7c357ce2af928115c6c6a42b7e2a4345', 'url': '/api/1/revision/7834ef7e7c357ce2af928115c6c6a42b7e2a4345/' # noqa }], 'type': 'tar', 'synthetic': True, }] # when rv = self.client.get('/api/1/revision/origin/1/log/') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revisions) self.assertFalse(rv.has_header('Link')) mock_service.lookup_revision_log_by.assert_called_once_with( '1', 'HEAD', None, 11) @patch('swh.web.api.views.revision.service') def test_api_revision_log_by_with_next(self, mock_service): # given stub_revisions = [] for i in range(27): stub_revisions.append({'id': str(i)}) mock_service.lookup_revision_log_by.return_value = stub_revisions[:26] expected_revisions = [x for x in stub_revisions if int(x['id']) < 25] for e in expected_revisions: e['url'] = '/api/1/revision/%s/' % e['id'] e['history_url'] = '/api/1/revision/%s/log/' % e['id'] # when rv = self.client.get('/api/1/revision/origin/1/log/?per_page=25') # then self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertIsNotNone(rv['Link']) self.assertEqual(rv.data, expected_revisions) mock_service.lookup_revision_log_by.assert_called_once_with( '1', 'HEAD', None, 26) @patch('swh.web.api.views.revision.service') def test_api_revision_log_by_norev(self, mock_service): # given mock_service.lookup_revision_log_by.side_effect = NotFoundExc( 'No revision') # when rv = self.client.get('/api/1/revision/origin/1/log/') # then self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertFalse(rv.has_header('Link')) self.assertEqual(rv.data, {'exception': 'NotFoundExc', 'reason': 'No revision'}) mock_service.lookup_revision_log_by.assert_called_once_with( '1', 'HEAD', None, 11) - @patch('swh.web.api.views.revision.service') - def test_api_revision_history(self, mock_service): - # for readability purposes, we use: - # - sha1 as 3 letters (url are way too long otherwise to respect pep8) - # - only keys with modification steps (all other keys are kept as is) - - # given - stub_revision = { - 'id': '883', - 'children': ['777', '999'], - 'parents': [], - 'directory': '272' - } - - mock_service.lookup_revision.return_value = stub_revision - - # then - rv = self.client.get('/api/1/revision/883/prev/999/') - - self.assertEqual(rv.status_code, 200) - self.assertEqual(rv['Content-Type'], 'application/json') - self.assertEqual(rv.data, { - 'id': '883', - 'url': '/api/1/revision/883/', - 'history_url': '/api/1/revision/883/log/', - 'history_context_url': '/api/1/revision/883/prev/999/log/', - 'children': ['777', '999'], - 'children_urls': ['/api/1/revision/777/', - '/api/1/revision/999/'], - 'parents': [], - 'directory': '272', - 'directory_url': '/api/1/directory/272/' - }) - - mock_service.lookup_revision.assert_called_once_with('883') - @patch('swh.web.api.views.revision._revision_directory_by') def test_api_revision_directory_ko_not_found(self, mock_rev_dir): # given mock_rev_dir.side_effect = NotFoundExc('Not found') # then rv = self.client.get('/api/1/revision/999/directory/some/path/to/dir/') self.assertEqual(rv.status_code, 404) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Not found'}) mock_rev_dir.assert_called_once_with( {'sha1_git': '999'}, 'some/path/to/dir', '/api/1/revision/999/directory/some/path/to/dir/', with_data=False) @patch('swh.web.api.views.revision._revision_directory_by') def test_api_revision_directory_ok_returns_dir_entries(self, mock_rev_dir): stub_dir = { 'type': 'dir', 'revision': '999', 'content': [ { 'sha1_git': '789', 'type': 'file', 'target': '101', 'target_url': '/api/1/content/sha1_git:101/', 'name': 'somefile', 'file_url': '/api/1/revision/999/directory/some/path/' 'somefile/' }, { 'sha1_git': '123', 'type': 'dir', 'target': '456', 'target_url': '/api/1/directory/456/', 'name': 'to-subdir', 'dir_url': '/api/1/revision/999/directory/some/path/' 'to-subdir/', }] } # given mock_rev_dir.return_value = stub_dir # then rv = self.client.get('/api/1/revision/999/directory/some/path/') self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, stub_dir) mock_rev_dir.assert_called_once_with( {'sha1_git': '999'}, 'some/path', '/api/1/revision/999/directory/some/path/', with_data=False) @patch('swh.web.api.views.revision._revision_directory_by') def test_api_revision_directory_ok_returns_content(self, mock_rev_dir): stub_content = { 'type': 'file', 'revision': '999', 'content': { 'sha1_git': '789', 'sha1': '101', 'data_url': '/api/1/content/101/raw/', } } # given mock_rev_dir.return_value = stub_content # then url = '/api/1/revision/666/directory/some/other/path/' rv = self.client.get(url) self.assertEqual(rv.status_code, 200) self.assertEqual(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, stub_content) mock_rev_dir.assert_called_once_with( {'sha1_git': '666'}, 'some/other/path', url, with_data=False)