diff --git a/swh/web/api/apiresponse.py b/swh/web/api/apiresponse.py index e88348cf..48dd4ca1 100644 --- a/swh/web/api/apiresponse.py +++ b/swh/web/api/apiresponse.py @@ -1,173 +1,177 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import json from rest_framework.response import Response from swh.storage.exc import StorageDBError, StorageAPIError from swh.web.common.exc import NotFoundExc, ForbiddenExc from swh.web.common.utils import shorten_path from swh.web.api import utils def compute_link_header(rv, options): """Add Link header in returned value results. Args: rv (dict): dictionary with keys: - headers: potential headers with 'link-next' and 'link-prev' keys - results: containing the result to return options (dict): the initial dict to update with result if any Returns: dict: dictionary with optional keys 'link-next' and 'link-prev' """ link_headers = [] if 'headers' not in rv: return {} rv_headers = rv['headers'] if 'link-next' in rv_headers: link_headers.append('<%s>; rel="next"' % ( rv_headers['link-next'])) if 'link-prev' in rv_headers: link_headers.append('<%s>; rel="previous"' % ( rv_headers['link-prev'])) if link_headers: link_header_str = ','.join(link_headers) headers = options.get('headers', {}) headers.update({ 'Link': link_header_str }) return headers return {} def filter_by_fields(request, data): """Extract a request parameter 'fields' if it exists to permit the filtering on he data dict's keys. If such field is not provided, returns the data as is. """ fields = request.query_params.get('fields') if fields: fields = set(fields.split(',')) data = utils.filter_field_keys(data, fields) return data def transform(rv): """Transform an eventual returned value with multiple layer of information with only what's necessary. If the returned value rv contains the 'results' key, this is the associated value which is returned. Otherwise, return the initial dict without the potential 'headers' key. """ if 'results' in rv: return rv['results'] if 'headers' in rv: rv.pop('headers') return rv def make_api_response(request, data, doc_data={}, options={}): """Generates an API response based on the requested mimetype. Args: request: a DRF Request object data: raw data to return in the API response doc_data: documentation data for HTML response options: optionnal data that can be used to generate the response Returns: a DRF Response a object """ if data: options['headers'] = compute_link_header(data, options) data = transform(data) data = filter_by_fields(request, data) doc_env = doc_data headers = {} if 'headers' in options: doc_env['headers_data'] = options['headers'] headers = options['headers'] # get request status code doc_env['status_code'] = options.get('status', 200) response_args = {'status': doc_env['status_code'], 'headers': headers, 'content_type': request.accepted_media_type} # when requesting HTML, typically when browsing the API through its # documented views, we need to enrich the input data with documentation # related ones and inform DRF that we request HTML template rendering if request.accepted_media_type == 'text/html': if data: data = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')) doc_env['response_data'] = data - doc_env['request'] = request + doc_env['request'] = { + 'path': request.path, + 'method': request.method, + 'absolute_uri': request.build_absolute_uri() + } doc_env['heading'] = shorten_path(str(request.path)) response_args['data'] = doc_env response_args['template_name'] = 'apidoc.html' # otherwise simply return the raw data and let DRF picks # the correct renderer (JSON or YAML) else: response_args['data'] = data return Response(**response_args) def error_response(request, error, doc_data): """Private function to create a custom error response. Args: request: a DRF Request object error: the exception that caused the error doc_data: documentation data for HTML response """ error_code = 400 if isinstance(error, NotFoundExc): error_code = 404 elif isinstance(error, ForbiddenExc): error_code = 403 elif isinstance(error, StorageDBError): error_code = 503 elif isinstance(error, StorageAPIError): error_code = 503 error_opts = {'status': error_code} error_data = { 'exception': error.__class__.__name__, 'reason': str(error), } return make_api_response(request, error_data, doc_data, options=error_opts) diff --git a/swh/web/settings/common.py b/swh/web/settings/common.py index 57cc078d..e80ca18b 100644 --- a/swh/web/settings/common.py +++ b/swh/web/settings/common.py @@ -1,189 +1,189 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information """ Django settings for swhweb project. Generated by 'django-admin startproject' using Django 1.11.3. For more information on this file, see https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ import os from swh.web.config import get_config swh_web_config = get_config() # Build paths inside the project like this: os.path.join(BASE_DIR, ...) PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = swh_web_config['secret_key'] # SECURITY WARNING: don't run with debug turned on in production! DEBUG = swh_web_config['debug'] DEBUG_PROPAGATE_EXCEPTIONS = swh_web_config['debug'] ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] + swh_web_config['allowed_hosts'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'swh.web.api', 'swh.web.browse' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware' ] ROOT_URLCONF = 'swh.web.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(PROJECT_DIR, "../templates")], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], 'libraries': { 'swh_templatetags': 'swh.web.common.swh_templatetags', }, }, }, ] WSGI_APPLICATION = 'swh.web.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(PROJECT_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', # noqa }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', # noqa }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', # noqa }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', # noqa }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(PROJECT_DIR, "../static") ] INTERNAL_IPS = ['127.0.0.1'] throttle_rates = {} throttling = swh_web_config['throttling'] for limiter_scope, limiter_conf in throttling['scopes'].items(): throttle_rates[limiter_scope] = limiter_conf['limiter_rate'] REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'swh.web.api.renderers.YAMLRenderer', 'rest_framework.renderers.TemplateHTMLRenderer' ), 'DEFAULT_THROTTLE_CLASSES': ( 'swh.web.common.throttling.SwhWebRateThrottle', ), 'DEFAULT_THROTTLE_RATES': throttle_rates } LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse', }, 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', }, 'file': { 'level': 'INFO', 'filters': ['require_debug_false'], 'class': 'logging.FileHandler', 'filename': os.path.join(swh_web_config['log_dir'], 'swh-web.log'), }, }, 'loggers': { 'django': { 'handlers': ['console', 'file'], 'level': 'DEBUG' if DEBUG else 'INFO', 'propagate': True, } }, } diff --git a/swh/web/settings/development.py b/swh/web/settings/development.py index eb931812..9306877b 100644 --- a/swh/web/settings/development.py +++ b/swh/web/settings/development.py @@ -1,7 +1,10 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from .common import * # noqa + +from django.core.cache import cache +cache.clear() diff --git a/swh/web/settings/production.py b/swh/web/settings/production.py index 2c8f6b50..9dcec89d 100644 --- a/swh/web/settings/production.py +++ b/swh/web/settings/production.py @@ -1,21 +1,25 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information - from .common import * # noqa from .common import swh_web_config +# activate per-site caching +MIDDLEWARE += ['django.middleware.cache.UpdateCacheMiddleware', # noqa + 'django.middleware.common.CommonMiddleware', + 'django.middleware.cache.FetchFromCacheMiddleware'] + CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': swh_web_config['throttling']['cache_uri'], } } # Setup support for proxy headers USE_X_FORWARDED_HOST = True SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') ALLOWED_HOSTS += ['archive.softwareheritage.org'] # noqa diff --git a/swh/web/templates/apidoc.html b/swh/web/templates/apidoc.html index 753eb1a6..8bb02235 100644 --- a/swh/web/templates/apidoc.html +++ b/swh/web/templates/apidoc.html @@ -1,149 +1,149 @@ {% extends "layout.html" %} {% load swh_templatetags %} {% load static %} {% block title %}{{ heading }} – Software Heritage API {% endblock %} {% block header %} - + {% endblock %} {% block content %} {% if docstring %}

Description

{{ docstring | safe_docstring_display | safe }}
{% endif %} {% if response_data %}

Request

-
{{ request.method }} {{ request.build_absolute_uri }}
+
{{ request.method }} {{ request.absolute_uri }}

Response

{% if status_code != 200 %}

Status Code

{{ status_code }}
{% endif %} {% if headers_data %}

Headers

{% for header_name, header_value in headers_data.items %}
{{ header_name }} {{ header_value | urlize_header_links | safe }}
{% endfor %} {% endif %}

Body

       {{ response_data | urlize_links_and_mails | safe }}
     
{% endif %}
{% if urls and urls|length > 0 %}
{% for url in urls %} {% endfor %}
URL Allowed Methods
{{ url.rule }} {{ url.methods | dictsort:0 | join:', ' }}

{% endif %} {% if args and args|length > 0 %}

Arguments

{% for arg in args %}
{{ arg.name }}: {{ arg.type }}
{{ arg.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if params and params|length > 0 %}

Parameters

{% for param in params %}
{{ param.name }}: {{ param.type }}
{{ param.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if headers and headers|length > 0 %}

Headers

{% for header in headers %}
{{ header.name }}: string
{{ header.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if returns and returns|length > 0 %}

Returns

{% for return in returns %}
{{ return.type }}
{{ return.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if excs and excs|length > 0 %}

Errors

{% for exc in excs %}
{{ exc.exc }}
{{ exc.doc | safe_docstring_display | safe }}
{% endfor %}

{% endif %} {% if examples and examples|length > 0 %}

Examples

{% for example in examples %}
{{ example }}
{% endfor %}
{% endif %} {% endblock %} diff --git a/swh/web/tests/api/test_api_lookup.py b/swh/web/tests/api/test_api_lookup.py index cfd43e64..35b534a7 100644 --- a/swh/web/tests/api/test_api_lookup.py +++ b/swh/web/tests/api/test_api_lookup.py @@ -1,126 +1,127 @@ # Copyright (C) 2015-2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information +import unittest + from nose.tools import istest -from .swh_api_testcase import SWHApiTestCase from swh.web.common.exc import NotFoundExc from swh.web.api.views import utils -class ApiLookupTestCase(SWHApiTestCase): +class ApiLookupTestCase(unittest.TestCase): @istest def genericapi_lookup_nothing_is_found(self): # given def test_generic_lookup_fn(sha1, another_unused_arg): assert another_unused_arg == 'unused_arg' assert sha1 == 'sha1' return None # when with self.assertRaises(NotFoundExc) as cm: utils.api_lookup( test_generic_lookup_fn, 'sha1', 'unused_arg', notfound_msg='This will be raised because None is returned.') self.assertIn('This will be raised because None is returned.', cm.exception.args[0]) @istest def generic_api_map_are_enriched_and_transformed_to_list(self): # given def test_generic_lookup_fn_1(criteria0, param0, param1): assert criteria0 == 'something' return map(lambda x: x + 1, [1, 2, 3]) # when actual_result = utils.api_lookup( test_generic_lookup_fn_1, 'something', 'some param 0', 'some param 1', notfound_msg=('This is not the error message you are looking for. ' 'Move along.'), enrich_fn=lambda x: x * 2) self.assertEqual(actual_result, [4, 6, 8]) @istest def generic_api_list_are_enriched_too(self): # given def test_generic_lookup_fn_2(crit): assert crit == 'something' return ['a', 'b', 'c'] # when actual_result = utils.api_lookup( test_generic_lookup_fn_2, 'something', notfound_msg=('Not the error message you are looking for, it is. ' 'Along, you move!'), enrich_fn=lambda x: ''. join(['=', x, '='])) self.assertEqual(actual_result, ['=a=', '=b=', '=c=']) @istest def generic_api_generator_are_enriched_and_returned_as_list(self): # given def test_generic_lookup_fn_3(crit): assert crit == 'crit' return (i for i in [4, 5, 6]) # when actual_result = utils.api_lookup( test_generic_lookup_fn_3, 'crit', notfound_msg='Move!', enrich_fn=lambda x: x - 1) self.assertEqual(actual_result, [3, 4, 5]) @istest def generic_api_simple_data_are_enriched_and_returned_too(self): # given def test_generic_lookup_fn_4(crit): assert crit == '123' return {'a': 10} def test_enrich_data(x): x['a'] = x['a'] * 10 return x # when actual_result = utils.api_lookup( test_generic_lookup_fn_4, '123', notfound_msg='Nothing to do', enrich_fn=test_enrich_data) self.assertEqual(actual_result, {'a': 100}) @istest def api_lookup_not_found(self): # when with self.assertRaises(NotFoundExc) as e: utils.api_lookup( lambda x: None, 'something', notfound_msg='this is the error message raised as it is None') self.assertEqual(e.exception.args[0], 'this is the error message raised as it is None') @istest def api_lookup_with_result(self): # when actual_result = utils.api_lookup( lambda x: x + '!', 'something', notfound_msg='this is the error which won\'t be used here') self.assertEqual(actual_result, 'something!') @istest def api_lookup_with_result_as_map(self): # when actual_result = utils.api_lookup( lambda x: map(lambda y: y+1, x), [1, 2, 3], notfound_msg='this is the error which won\'t be used here') self.assertEqual(actual_result, [2, 3, 4]) diff --git a/swh/web/tests/api/test_apidoc.py b/swh/web/tests/api/test_apidoc.py index 7c7973f4..d6381a99 100644 --- a/swh/web/tests/api/test_apidoc.py +++ b/swh/web/tests/api/test_apidoc.py @@ -1,121 +1,122 @@ # Copyright (C) 2015 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 nose.tools import istest, nottest +from rest_framework.test import APITestCase from rest_framework.response import Response -from .swh_api_testcase import SWHApiTestCase from swh.web.api import apidoc from swh.web.api.apiurls import api_route +from swh.web.tests.testbase import SWHWebTestBase -class APIDocTestCase(SWHApiTestCase): +class APIDocTestCase(SWHWebTestBase, APITestCase): def setUp(self): self.arg_dict = { 'name': 'my_pretty_arg', 'default': 'some default value', 'type': apidoc.argtypes.sha1, 'doc': 'this arg does things' } self.stub_excs = [{'exc': apidoc.excs.badinput, 'doc': 'My exception documentation'}] self.stub_args = [{'name': 'stub_arg', 'default': 'some_default'}] self.stub_rule_list = [ {'rule': 'some/route/with/args/', 'methods': {'GET', 'HEAD', 'OPTIONS'}}, {'rule': 'some/doc/route/', 'methods': {'GET', 'HEAD', 'OPTIONS'}}, {'rule': 'some/other/route/', 'methods': {'GET', 'HEAD', 'OPTIONS'}} ] self.stub_return = { 'type': apidoc.rettypes.dict.value, 'doc': 'a dict with amazing properties' } @staticmethod @apidoc.route('/my/nodoc/url/') @nottest def apidoc_nodoc_tester(request, arga=0, argb=0): return Response(arga + argb) @istest def apidoc_nodoc_failure(self): with self.assertRaises(Exception): self.client.get('/api/1/my/nodoc/url/') @staticmethod @api_route(r'/some/(?P[0-9]+)/(?P[0-9]+)/', 'some-doc-route') @apidoc.route('/some/doc/route/') @nottest def apidoc_route_tester(request, myarg, myotherarg, akw=0): """ Sample doc """ return {'result': int(myarg) + int(myotherarg) + akw} @istest def apidoc_route_doc(self): # when rv = self.client.get('/api/1/some/doc/route/') # then self.assertEqual(rv.status_code, 200) self.assertTemplateUsed('apidoc.html') @istest def apidoc_route_fn(self): # when rv = self.client.get('/api/1/some/1/1/') # then self.assertEqual(rv.status_code, 200) @staticmethod @api_route(r'/some/full/(?P[0-9]+)/(?P[0-9]+)/', 'some-complete-doc-route') @apidoc.route('/some/complete/doc/route/') @apidoc.arg('myarg', default=67, argtype=apidoc.argtypes.int, argdoc='my arg') @apidoc.arg('myotherarg', default=42, argtype=apidoc.argtypes.int, argdoc='my other arg') @apidoc.param('limit', argtype=apidoc.argtypes.int, default=10, doc='Result limitation') @apidoc.header('Link', doc='Header link returns for pagination purpose') @apidoc.raises(exc=apidoc.excs.badinput, doc='Oops') @apidoc.returns(rettype=apidoc.rettypes.dict, retdoc='sum of args') @nottest def apidoc_full_stack_tester(request, myarg, myotherarg, akw=0): """ Sample doc """ return {'result': int(myarg) + int(myotherarg) + akw} @istest def apidoc_full_stack_doc(self): # when rv = self.client.get('/api/1/some/complete/doc/route/') # then self.assertEqual(rv.status_code, 200) self.assertTemplateUsed('apidoc.html') @istest def apidoc_full_stack_fn(self): # when rv = self.client.get('/api/1/some/full/1/1/') # then self.assertEqual(rv.status_code, 200) diff --git a/swh/web/tests/api/test_apiresponse.py b/swh/web/tests/api/test_apiresponse.py index 0d204ba5..22dd353e 100644 --- a/swh/web/tests/api/test_apiresponse.py +++ b/swh/web/tests/api/test_apiresponse.py @@ -1,176 +1,180 @@ # Copyright (C) 2015-2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information import json import unittest from rest_framework.test import APIRequestFactory from nose.tools import istest from unittest.mock import patch from swh.web.api.apiresponse import ( compute_link_header, transform, make_api_response, filter_by_fields ) api_request_factory = APIRequestFactory() class SWHComputeLinkHeaderTest(unittest.TestCase): @istest def compute_link_header(self): rv = { 'headers': {'link-next': 'foo', 'link-prev': 'bar'}, 'results': [1, 2, 3] } options = {} # when headers = compute_link_header( rv, options) self.assertEquals(headers, { 'Link': '; rel="next",; rel="previous"', }) @istest def compute_link_header_nothing_changed(self): rv = {} options = {} # when headers = compute_link_header( rv, options) self.assertEquals(headers, {}) @istest def compute_link_header_nothing_changed_2(self): rv = {'headers': {}} options = {} # when headers = compute_link_header( rv, options) self.assertEquals(headers, {}) class SWHTransformProcessorTest(unittest.TestCase): @istest def transform_only_return_results_1(self): rv = {'results': {'some-key': 'some-value'}} self.assertEquals(transform(rv), {'some-key': 'some-value'}) @istest def transform_only_return_results_2(self): rv = {'headers': {'something': 'do changes'}, 'results': {'some-key': 'some-value'}} self.assertEquals(transform(rv), {'some-key': 'some-value'}) @istest def transform_do_remove_headers(self): rv = {'headers': {'something': 'do changes'}, 'some-key': 'some-value'} self.assertEquals(transform(rv), {'some-key': 'some-value'}) @istest def transform_do_nothing(self): rv = {'some-key': 'some-value'} self.assertEquals(transform(rv), {'some-key': 'some-value'}) class RendererTestCase(unittest.TestCase): @patch('swh.web.api.apiresponse.json') @patch('swh.web.api.apiresponse.filter_by_fields') @patch('swh.web.api.apiresponse.shorten_path') @istest def swh_multi_response_mimetype(self, mock_shorten_path, mock_filter, mock_json): # given data = { 'data': [12, 34], 'id': 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc' } mock_filter.return_value = data mock_shorten_path.return_value = 'my_short_path' accepted_response_formats = {'html': 'text/html', 'yaml': 'application/yaml', 'json': 'application/json'} for format in accepted_response_formats: request = api_request_factory.get('/api/test/path/') mime_type = accepted_response_formats[format] setattr(request, 'accepted_media_type', mime_type) if mime_type == 'text/html': expected_data = { 'response_data': json.dumps(data), - 'request': request, + 'request': { + 'path': request.path, + 'method': request.method, + 'absolute_uri': request.build_absolute_uri() + }, 'headers_data': {}, 'heading': 'my_short_path', 'status_code': 200 } mock_json.dumps.return_value = json.dumps(data) else: expected_data = data # when rv = make_api_response(request, data) # then mock_filter.assert_called_with(request, data) self.assertEqual(rv.data, expected_data) self.assertEqual(rv.status_code, 200) if mime_type == 'text/html': self.assertEqual(rv.template_name, 'apidoc.html') @istest def swh_filter_renderer_do_nothing(self): # given input_data = {'a': 'some-data'} request = api_request_factory.get('/api/test/path/', data={}) setattr(request, 'query_params', request.GET) # when actual_data = filter_by_fields(request, input_data) # then self.assertEquals(actual_data, input_data) @patch('swh.web.api.apiresponse.utils.filter_field_keys') @istest def swh_filter_renderer_do_filter(self, mock_ffk): # given mock_ffk.return_value = {'a': 'some-data'} request = api_request_factory.get('/api/test/path/', data={'fields': 'a,c'}) setattr(request, 'query_params', request.GET) input_data = {'a': 'some-data', 'b': 'some-other-data'} # when actual_data = filter_by_fields(request, input_data) # then self.assertEquals(actual_data, {'a': 'some-data'}) mock_ffk.assert_called_once_with(input_data, {'a', 'c'}) diff --git a/swh/web/tests/api/views/test_content.py b/swh/web/tests/api/views/test_content.py index 338fbe2c..7cdcda51 100644 --- a/swh/web/tests/api/views/test_content.py +++ b/swh/web/tests/api/views/test_content.py @@ -1,730 +1,731 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch, MagicMock -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class ContentApiTestCase(SWHApiTestCase): +class ContentApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.content.service') @istest def api_content_filetype(self, mock_service): stub_filetype = { 'accepted_media_type': 'application/xml', 'encoding': 'ascii', 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', } mock_service.lookup_content_filetype.return_value = stub_filetype # when rv = self.client.get( '/api/1/content/' 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f/filetype/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'accepted_media_type': 'application/xml', 'encoding': 'ascii', 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'content_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/', }) mock_service.lookup_content_filetype.assert_called_once_with( 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f') @patch('swh.web.api.views.content.service') @istest def api_content_filetype_sha_not_found(self, mock_service): # given mock_service.lookup_content_filetype.return_value = None # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/' 'filetype/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No filetype information found for content ' 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03.' }) mock_service.lookup_content_filetype.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_language(self, mock_service): stub_language = { 'lang': 'lisp', 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', } mock_service.lookup_content_language.return_value = stub_language # when rv = self.client.get( '/api/1/content/' 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f/language/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'lang': 'lisp', 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'content_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/', }) mock_service.lookup_content_language.assert_called_once_with( 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f') @patch('swh.web.api.views.content.service') @istest def api_content_language_sha_not_found(self, mock_service): # given mock_service.lookup_content_language.return_value = None # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03' '/language/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No language information found for content ' 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03.' }) mock_service.lookup_content_language.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_symbol(self, mock_service): stub_ctag = [{ 'sha1': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'name': 'foobar', 'kind': 'Haskell', 'line': 10, }] mock_service.lookup_expression.return_value = stub_ctag # when rv = self.client.get('/api/1/content/symbol/foo/?last_sha1=sha1') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, [{ 'sha1': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'name': 'foobar', 'kind': 'Haskell', 'line': 10, 'content_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/', 'data_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/raw/', 'license_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/license/', 'language_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/language/', 'filetype_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/filetype/', }]) self.assertFalse('Link' in rv) mock_service.lookup_expression.assert_called_once_with( 'foo', 'sha1', 10) @patch('swh.web.api.views.content.service') @istest def api_content_symbol_2(self, mock_service): stub_ctag = [{ 'sha1': '12371b8614fcd89ccd17ca2b1d9e66c5b00a6456', 'name': 'foobar', 'kind': 'Haskell', 'line': 10, }, { 'sha1': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6678', 'name': 'foo', 'kind': 'Lisp', 'line': 10, }] mock_service.lookup_expression.return_value = stub_ctag # when rv = self.client.get( '/api/1/content/symbol/foo/?last_sha1=prev-sha1&per_page=2') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, stub_ctag) self.assertTrue( rv['Link'] == '; rel="next"' or # noqa rv['Link'] == '; rel="next"' # noqa ) mock_service.lookup_expression.assert_called_once_with( 'foo', 'prev-sha1', 2) @patch('swh.web.api.views.content.service') # @istest def api_content_symbol_3(self, mock_service): stub_ctag = [{ 'sha1': '67891b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'name': 'foo', 'kind': 'variable', 'line': 100, }] mock_service.lookup_expression.return_value = stub_ctag # when rv = self.client.get('/api/1/content/symbol/foo/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, [{ 'sha1': '67891b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'name': 'foo', 'kind': 'variable', 'line': 100, 'content_url': '/api/1/content/' 'sha1:67891b8614fcd89ccd17ca2b1d9e66c5b00a6d03/', 'data_url': '/api/1/content/' 'sha1:67891b8614fcd89ccd17ca2b1d9e66c5b00a6d03/raw/', 'license_url': '/api/1/content/' 'sha1:67891b8614fcd89ccd17ca2b1d9e66c5b00a6d03/license/', 'language_url': '/api/1/content/' 'sha1:67891b8614fcd89ccd17ca2b1d9e66c5b00a6d03/language/', 'filetype_url': '/api/1/content/' 'sha1:67891b8614fcd89ccd17ca2b1d9e66c5b00a6d03/filetype/', }]) self.assertFalse(rv.has_header('Link')) mock_service.lookup_expression.assert_called_once_with('foo', None, 10) @patch('swh.web.api.views.content.service') @istest def api_content_symbol_not_found(self, mock_service): # given mock_service.lookup_expression.return_value = [] # when rv = self.client.get('/api/1/content/symbol/bar/?last_sha1=hash') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No indexed raw content match expression \'bar\'.' }) self.assertFalse('Link' in rv) mock_service.lookup_expression.assert_called_once_with( 'bar', 'hash', 10) @patch('swh.web.api.views.content.service') @istest def api_content_ctags(self, mock_service): stub_ctags = { 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'ctags': [] } mock_service.lookup_content_ctags.return_value = stub_ctags # when rv = self.client.get( '/api/1/content/' 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f/ctags/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'ctags': [], 'content_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/', }) mock_service.lookup_content_ctags.assert_called_once_with( 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f') @patch('swh.web.api.views.content.service') @istest def api_content_license(self, mock_service): stub_license = { 'licenses': ['No_license_found', 'Apache-2.0'], 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'tool_name': 'nomos', } mock_service.lookup_content_license.return_value = stub_license # when rv = self.client.get( '/api/1/content/' 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f/license/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'licenses': ['No_license_found', 'Apache-2.0'], 'id': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'tool_name': 'nomos', 'content_url': '/api/1/content/' 'sha1:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/', }) mock_service.lookup_content_license.assert_called_once_with( 'sha1_git:b04caf10e9535160d90e874b45aa426de762f19f') @patch('swh.web.api.views.content.service') @istest def api_content_license_sha_not_found(self, mock_service): # given mock_service.lookup_content_license.return_value = None # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/' 'license/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No license information found for content ' 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03.' }) mock_service.lookup_content_license.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_provenance(self, mock_service): stub_provenances = [{ 'origin': 1, 'visit': 2, 'revision': 'b04caf10e9535160d90e874b45aa426de762f19f', 'content': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'path': 'octavio-3.4.0/octave.html/doc_002dS_005fISREG.html' }] mock_service.lookup_content_provenance.return_value = stub_provenances # when rv = self.client.get( '/api/1/content/' 'sha1_git:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/provenance/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, [{ 'origin': 1, 'visit': 2, 'origin_url': '/api/1/origin/1/', 'origin_visits_url': '/api/1/origin/1/visits/', 'origin_visit_url': '/api/1/origin/1/visit/2/', 'revision': 'b04caf10e9535160d90e874b45aa426de762f19f', 'revision_url': '/api/1/revision/' 'b04caf10e9535160d90e874b45aa426de762f19f/', 'content': '34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'content_url': '/api/1/content/' 'sha1_git:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03/', 'path': 'octavio-3.4.0/octave.html/doc_002dS_005fISREG.html' }]) mock_service.lookup_content_provenance.assert_called_once_with( 'sha1_git:34571b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_provenance_sha_not_found(self, mock_service): # given mock_service.lookup_content_provenance.return_value = None # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/' 'provenance/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Content with sha1:40e71b8614fcd89ccd17ca2b1d9e6' '6c5b00a6d03 not found.' }) mock_service.lookup_content_provenance.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def test_api_content_metadata(self, mock_service): # given mock_service.lookup_content.return_value = { 'checksums': { 'blake2s256': '685395c5dc57cada459364f0946d3dd45bad5f' 'cbabc1048edb44380f1d31d0aa', 'sha1': '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'sha1_git': 'b4e8f472ffcb01a03875b26e462eb568739f6882', 'sha256': '83c0e67cc80f60caf1fcbec2d84b0ccd7968b3be47' '35637006560cde9b067a4f', }, 'length': 17, 'status': 'visible' } # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'checksums': { 'blake2s256': '685395c5dc57cada459364f0946d3dd45bad5f' 'cbabc1048edb44380f1d31d0aa', 'sha1': '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03', 'sha1_git': 'b4e8f472ffcb01a03875b26e462eb568739f6882', 'sha256': '83c0e67cc80f60caf1fcbec2d84b0ccd7968b3be47' '35637006560cde9b067a4f', }, 'data_url': '/api/1/content/' 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/raw/', 'filetype_url': '/api/1/content/' 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/filetype/', 'language_url': '/api/1/content/' 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/language/', 'license_url': '/api/1/content/' 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03/license/', 'length': 17, 'status': 'visible' }) mock_service.lookup_content.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_not_found_as_json(self, mock_service): # given mock_service.lookup_content.return_value = None mock_service.lookup_content_provenance = MagicMock() # when rv = self.client.get( '/api/1/content/sha256:83c0e67cc80f60caf1fcbec2d84b0ccd7968b3' 'be4735637006560c/') self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Content with sha256:83c0e67cc80f60caf1fcbec2d84b0ccd79' '68b3be4735637006560c not found.' }) mock_service.lookup_content.assert_called_once_with( 'sha256:83c0e67cc80f60caf1fcbec2d84b0ccd7968b3' 'be4735637006560c') mock_service.lookup_content_provenance.called = False @patch('swh.web.api.views.content.service') @istest def api_content_not_found_as_yaml(self, mock_service): # given mock_service.lookup_content.return_value = None mock_service.lookup_content_provenance = MagicMock() # when rv = self.client.get( '/api/1/content/sha256:83c0e67cc80f60caf1fcbec2d84b0ccd7968b3' 'be4735637006560c/', HTTP_ACCEPT='application/yaml') self.assertEquals(rv.status_code, 404) self.assertTrue('application/yaml' in rv['Content-Type']) self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Content with sha256:83c0e67cc80f60caf1fcbec2d84b0ccd79' '68b3be4735637006560c not found.' }) mock_service.lookup_content.assert_called_once_with( 'sha256:83c0e67cc80f60caf1fcbec2d84b0ccd7968b3' 'be4735637006560c') mock_service.lookup_content_provenance.called = False @patch('swh.web.api.views.content.service') @istest def api_content_raw_ko_not_found(self, mock_service): # given mock_service.lookup_content_raw.return_value = None # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03' '/raw/') self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Content sha1:40e71b8614fcd89ccd17ca2b1d9e6' '6c5b00a6d03 is not found.' }) mock_service.lookup_content_raw.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_raw_text(self, mock_service): # given stub_content = {'data': b'some content data'} mock_service.lookup_content_raw.return_value = stub_content mock_service.lookup_content_filetype.return_value = { 'mimetype': 'text/html' } # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03' '/raw/') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/octet-stream') self.assertEquals( rv['Content-disposition'], 'attachment; filename=content_sha1_' '40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03_raw') self.assertEquals( rv['Content-Type'], 'application/octet-stream') self.assertEquals(rv.content, stub_content['data']) mock_service.lookup_content_raw.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') mock_service.lookup_content_filetype.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_raw_text_with_filename(self, mock_service): # given stub_content = {'data': b'some content data'} mock_service.lookup_content_raw.return_value = stub_content mock_service.lookup_content_filetype.return_value = { 'mimetype': 'text/html' } # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03' '/raw/?filename=filename.txt') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/octet-stream') self.assertEquals( rv['Content-disposition'], 'attachment; filename=filename.txt') self.assertEquals( rv['Content-Type'], 'application/octet-stream') self.assertEquals(rv.content, stub_content['data']) mock_service.lookup_content_raw.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') mock_service.lookup_content_filetype.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_raw_no_accepted_media_type_text_is_not_available_for_download( # noqa self, mock_service): # given stub_content = {'data': b'some content data'} mock_service.lookup_content_raw.return_value = stub_content mock_service.lookup_content_filetype.return_value = { 'mimetype': 'application/octet-stream' } # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03' '/raw/') self.assertEquals(rv.status_code, 403) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'ForbiddenExc', 'reason': 'Only textual content is available for download. ' 'Actual content mimetype is application/octet-stream.' }) mock_service.lookup_content_raw.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') mock_service.lookup_content_filetype.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_content_raw_no_accepted_media_type_found_so_not_available_for_download( # noqa self, mock_service): # given stub_content = {'data': b'some content data'} mock_service.lookup_content_raw.return_value = stub_content mock_service.lookup_content_filetype.return_value = None # when rv = self.client.get( '/api/1/content/sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03' '/raw/') self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Content sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03 ' 'is not available for download.' }) mock_service.lookup_content_raw.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') mock_service.lookup_content_filetype.assert_called_once_with( 'sha1:40e71b8614fcd89ccd17ca2b1d9e66c5b00a6d03') @patch('swh.web.api.views.content.service') @istest def api_check_content_known(self, mock_service): # given mock_service.lookup_multiple_hashes.return_value = [ {'found': True, 'filename': None, 'sha1': 'sha1:blah'} ] expected_result = { 'search_stats': {'nbfiles': 1, 'pct': 100}, 'search_res': [{'sha1': 'sha1:blah', 'found': True}] } # when rv = self.client.get('/api/1/content/known/sha1:blah/') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_result) mock_service.lookup_multiple_hashes.assert_called_once_with( [{'filename': None, 'sha1': 'sha1:blah'}]) @patch('swh.web.api.views.content.service') @istest def api_check_content_known_as_yaml(self, mock_service): # given mock_service.lookup_multiple_hashes.return_value = [ {'found': True, 'filename': None, 'sha1': 'sha1:halb'}, {'found': False, 'filename': None, 'sha1': 'sha1_git:hello'} ] expected_result = { 'search_stats': {'nbfiles': 2, 'pct': 50}, 'search_res': [{'sha1': 'sha1:halb', 'found': True}, {'sha1': 'sha1_git:hello', 'found': False}] } # when rv = self.client.get('/api/1/content/known/sha1:halb,sha1_git:hello/', HTTP_ACCEPT='application/yaml') self.assertEquals(rv.status_code, 200) self.assertTrue('application/yaml' in rv['Content-Type']) self.assertEquals(rv.data, expected_result) mock_service.lookup_multiple_hashes.assert_called_once_with( [{'filename': None, 'sha1': 'sha1:halb'}, {'filename': None, 'sha1': 'sha1_git:hello'}]) @patch('swh.web.api.views.content.service') @istest def api_check_content_known_post_as_yaml(self, mock_service): # given stub_result = [{'sha1': '7e62b1fe10c88a3eddbba930b156bee2956b2435', 'found': True}, {'filename': 'filepath', 'sha1': '8e62b1fe10c88a3eddbba930b156bee2956b2435', 'found': True}, {'filename': 'filename', 'sha1': '64025b5d1520c615061842a6ce6a456cad962a3f', 'found': False}] mock_service.lookup_multiple_hashes.return_value = stub_result expected_result = { 'search_stats': {'nbfiles': 3, 'pct': 2/3 * 100}, 'search_res': stub_result } # when rv = self.client.post( '/api/1/content/known/search/', data=dict( q='7e62b1fe10c88a3eddbba930b156bee2956b2435', filepath='8e62b1fe10c88a3eddbba930b156bee2956b2435', filename='64025b5d1520c615061842a6ce6a456cad962a3f'), HTTP_ACCEPT='application/yaml' ) self.assertEquals(rv.status_code, 200) self.assertTrue('application/yaml' in rv['Content-Type']) self.assertEquals(rv.data, expected_result) @patch('swh.web.api.views.content.service') @istest def api_check_content_known_not_found(self, mock_service): # given stub_result = [{'sha1': 'sha1:halb', 'found': False}] mock_service.lookup_multiple_hashes.return_value = stub_result expected_result = { 'search_stats': {'nbfiles': 1, 'pct': 0.0}, 'search_res': stub_result } # when rv = self.client.get('/api/1/content/known/sha1:halb/') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_result) mock_service.lookup_multiple_hashes.assert_called_once_with( [{'filename': None, 'sha1': 'sha1:halb'}]) diff --git a/swh/web/tests/api/views/test_directory.py b/swh/web/tests/api/views/test_directory.py index 51ab49fd..39ac5395 100644 --- a/swh/web/tests/api/views/test_directory.py +++ b/swh/web/tests/api/views/test_directory.py @@ -1,126 +1,127 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class DirectoryApiTestCase(SWHApiTestCase): +class DirectoryApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.directory.service') @istest def api_directory(self, mock_service): # given stub_directories = [ { 'sha1_git': '18d8be353ed3480476f032475e7c233eff7371d5', 'type': 'file', 'target': '4568be353ed3480476f032475e7c233eff737123', }, { 'sha1_git': '1d518d8be353ed3480476f032475e7c233eff737', 'type': 'dir', 'target': '8be353ed3480476f032475e7c233eff737123456', }] expected_directories = [ { 'sha1_git': '18d8be353ed3480476f032475e7c233eff7371d5', 'type': 'file', 'target': '4568be353ed3480476f032475e7c233eff737123', 'target_url': '/api/1/content/' 'sha1_git:4568be353ed3480476f032475e7c233eff737123/', }, { 'sha1_git': '1d518d8be353ed3480476f032475e7c233eff737', 'type': 'dir', 'target': '8be353ed3480476f032475e7c233eff737123456', 'target_url': '/api/1/directory/8be353ed3480476f032475e7c233eff737123456/', }] mock_service.lookup_directory.return_value = stub_directories # when rv = self.client.get('/api/1/directory/' '18d8be353ed3480476f032475e7c233eff7371d5/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_directories) mock_service.lookup_directory.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.directory.service') @istest def api_directory_not_found(self, mock_service): # given mock_service.lookup_directory.return_value = [] # when rv = self.client.get('/api/1/directory/' '66618d8be353ed3480476f032475e7c233eff737/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Directory with sha1_git ' '66618d8be353ed3480476f032475e7c233eff737 not found.'}) @patch('swh.web.api.views.directory.service') @istest def api_directory_with_path_found(self, mock_service): # given expected_dir = { 'sha1_git': '18d8be353ed3480476f032475e7c233eff7371d5', 'type': 'file', 'name': 'bla', 'target': '4568be353ed3480476f032475e7c233eff737123', 'target_url': '/api/1/content/' 'sha1_git:4568be353ed3480476f032475e7c233eff737123/', } mock_service.lookup_directory_with_path.return_value = expected_dir # when rv = self.client.get('/api/1/directory/' '18d8be353ed3480476f032475e7c233eff7371d5/bla/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_dir) mock_service.lookup_directory_with_path.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5', 'bla') @patch('swh.web.api.views.directory.service') @istest def api_directory_with_path_not_found(self, mock_service): # given mock_service.lookup_directory_with_path.return_value = None path = 'some/path/to/dir/' # when rv = self.client.get(('/api/1/directory/' '66618d8be353ed3480476f032475e7c233eff737/%s') % path) path = path.strip('/') # Path stripped of lead/trail separators # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': (('Entry with path %s relative to ' 'directory with sha1_git ' '66618d8be353ed3480476f032475e7c233eff737 not found.') % path)}) diff --git a/swh/web/tests/api/views/test_entity.py b/swh/web/tests/api/views/test_entity.py index 83d3b563..8c7457cf 100644 --- a/swh/web/tests/api/views/test_entity.py +++ b/swh/web/tests/api/views/test_entity.py @@ -1,94 +1,95 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch from swh.web.common.exc import BadInputExc -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class EntityApiTestCase(SWHApiTestCase): +class EntityApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.entity.service') @istest def api_lookup_entity_by_uuid_not_found(self, mock_service): # when mock_service.lookup_entity_by_uuid.return_value = [] # when rv = self.client.get('/api/1/entity/' '5f4d4c51-498a-4e28-88b3-b3e4e8396cba/') self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': "Entity with uuid '5f4d4c51-498a-4e28-88b3-b3e4e8396cba' not " + "found."}) mock_service.lookup_entity_by_uuid.assert_called_once_with( '5f4d4c51-498a-4e28-88b3-b3e4e8396cba') @patch('swh.web.api.views.entity.service') @istest def api_lookup_entity_by_uuid_bad_request(self, mock_service): # when mock_service.lookup_entity_by_uuid.side_effect = BadInputExc( 'bad input: uuid malformed!') # when rv = self.client.get('/api/1/entity/uuid malformed/') self.assertEquals(rv.status_code, 400) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'BadInputExc', 'reason': 'bad input: uuid malformed!'}) mock_service.lookup_entity_by_uuid.assert_called_once_with( 'uuid malformed') @patch('swh.web.api.views.entity.service') @istest def api_lookup_entity_by_uuid(self, mock_service): # when stub_entities = [ { 'uuid': '34bd6b1b-463f-43e5-a697-785107f598e4', 'parent': 'aee991a0-f8d7-4295-a201-d1ce2efc9fb2' }, { 'uuid': 'aee991a0-f8d7-4295-a201-d1ce2efc9fb2' } ] mock_service.lookup_entity_by_uuid.return_value = stub_entities expected_entities = [ { 'uuid': '34bd6b1b-463f-43e5-a697-785107f598e4', 'uuid_url': '/api/1/entity/34bd6b1b-463f-43e5-a697-' '785107f598e4/', 'parent': 'aee991a0-f8d7-4295-a201-d1ce2efc9fb2', 'parent_url': '/api/1/entity/aee991a0-f8d7-4295-a201-' 'd1ce2efc9fb2/' }, { 'uuid': 'aee991a0-f8d7-4295-a201-d1ce2efc9fb2', 'uuid_url': '/api/1/entity/aee991a0-f8d7-4295-a201-' 'd1ce2efc9fb2/' } ] # when rv = self.client.get('/api/1/entity' '/34bd6b1b-463f-43e5-a697-785107f598e4/') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_entities) mock_service.lookup_entity_by_uuid.assert_called_once_with( '34bd6b1b-463f-43e5-a697-785107f598e4') diff --git a/swh/web/tests/api/views/test_origin.py b/swh/web/tests/api/views/test_origin.py index f6878e2d..9bf93fec 100644 --- a/swh/web/tests/api/views/test_origin.py +++ b/swh/web/tests/api/views/test_origin.py @@ -1,249 +1,250 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch from swh.storage.exc import StorageDBError, StorageAPIError -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class OriginApiTestCase(SWHApiTestCase): +class OriginApiTestCase(SWHWebTestBase, APITestCase): def setUp(self): self.origin_visit1 = { 'date': 1104616800.0, 'origin': 10, 'visit': 100, 'metadata': None, 'status': 'full', } self.origin1 = { 'id': 1234, 'lister': 'uuid-lister-0', 'project': 'uuid-project-0', 'url': 'ftp://some/url/to/origin/0', 'type': 'ftp' } @patch('swh.web.api.views.origin.service') @istest def api_1_lookup_origin_visits_raise_error(self, mock_service): # given mock_service.lookup_origin_visits.side_effect = ValueError( 'voluntary error to check the bad request middleware.') # when rv = self.client.get('/api/1/origin/2/visits/') # then self.assertEquals(rv.status_code, 400) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'ValueError', 'reason': 'voluntary error to check the bad request middleware.'}) @patch('swh.web.api.views.origin.service') @istest def api_1_lookup_origin_visits_raise_swh_storage_error_db( self, mock_service): # given mock_service.lookup_origin_visits.side_effect = StorageDBError( 'SWH Storage exploded! Will be back online shortly!') # when rv = self.client.get('/api/1/origin/2/visits/') # then self.assertEquals(rv.status_code, 503) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'StorageDBError', 'reason': 'An unexpected error occurred in the backend: ' 'SWH Storage exploded! Will be back online shortly!'}) @patch('swh.web.api.views.origin.service') @istest def api_1_lookup_origin_visits_raise_swh_storage_error_api( self, mock_service): # given mock_service.lookup_origin_visits.side_effect = StorageAPIError( 'SWH Storage API dropped dead! Will resurrect from its ashes asap!' ) # when rv = self.client.get('/api/1/origin/2/visits/') # then self.assertEquals(rv.status_code, 503) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'StorageAPIError', 'reason': 'An unexpected error occurred in the api backend: ' 'SWH Storage API dropped dead! Will resurrect from its ashes asap!' }) @patch('swh.web.api.views.origin.service') @istest def api_1_lookup_origin_visits(self, mock_service): # given stub_visits = [ { 'date': 1293919200.0, 'origin': 2, 'visit': 2 }, { 'date': 1420149600.0, 'origin': 2, 'visit': 3 } ] mock_service.lookup_origin_visits.return_value = stub_visits # when rv = self.client.get('/api/1/origin/2/visits/?per_page=2&last_visit=1') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, [ { 'date': 1293919200.0, 'origin': 2, 'visit': 2, 'origin_visit_url': '/api/1/origin/2/visit/2/', }, { 'date': 1420149600.0, 'origin': 2, 'visit': 3, 'origin_visit_url': '/api/1/origin/2/visit/3/', } ]) mock_service.lookup_origin_visits.assert_called_once_with( '2', last_visit=1, per_page=2) @patch('swh.web.api.views.origin.service') @istest def api_1_lookup_origin_visit(self, mock_service): # given origin_visit = self.origin_visit1.copy() origin_visit.update({ 'occurrences': { 'master': { 'target_type': 'revision', 'target': '98564', } } }) mock_service.lookup_origin_visit.return_value = origin_visit expected_origin_visit = self.origin_visit1.copy() expected_origin_visit.update({ 'origin_url': '/api/1/origin/10/', 'occurrences': { 'master': { 'target_type': 'revision', 'target': '98564', 'target_url': '/api/1/revision/98564/' } } }) # when rv = self.client.get('/api/1/origin/10/visit/100/') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_origin_visit) mock_service.lookup_origin_visit.assert_called_once_with('10', '100') @patch('swh.web.api.views.origin.service') @istest def api_1_lookup_origin_visit_not_found(self, mock_service): # given mock_service.lookup_origin_visit.return_value = None # when rv = self.client.get('/api/1/origin/1/visit/1000/') self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No visit 1000 for origin 1 found' }) mock_service.lookup_origin_visit.assert_called_once_with('1', '1000') @patch('swh.web.api.views.origin.service') @istest def api_origin_by_id(self, mock_service): # given mock_service.lookup_origin.return_value = self.origin1 expected_origin = self.origin1.copy() expected_origin.update({ 'origin_visits_url': '/api/1/origin/1234/visits/' }) # when rv = self.client.get('/api/1/origin/1234/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_origin) mock_service.lookup_origin.assert_called_with({'id': '1234'}) @patch('swh.web.api.views.origin.service') @istest def api_origin_by_type_url(self, mock_service): # given stub_origin = self.origin1.copy() stub_origin.update({ 'id': 987 }) mock_service.lookup_origin.return_value = stub_origin expected_origin = stub_origin.copy() expected_origin.update({ 'origin_visits_url': '/api/1/origin/987/visits/' }) # when rv = self.client.get('/api/1/origin/ftp/url' '/ftp://some/url/to/origin/0/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_origin) mock_service.lookup_origin.assert_called_with( {'url': 'ftp://some/url/to/origin/0/', 'type': 'ftp'}) @patch('swh.web.api.views.origin.service') @istest def api_origin_not_found(self, mock_service): # given mock_service.lookup_origin.return_value = None # when rv = self.client.get('/api/1/origin/4321/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Origin with id 4321 not found.' }) mock_service.lookup_origin.assert_called_with({'id': '4321'}) diff --git a/swh/web/tests/api/views/test_person.py b/swh/web/tests/api/views/test_person.py index 38969820..56a43772 100644 --- a/swh/web/tests/api/views/test_person.py +++ b/swh/web/tests/api/views/test_person.py @@ -1,47 +1,48 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class PersonApiTestCase(SWHApiTestCase): +class PersonApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.person.service') @istest def api_person(self, mock_service): # given stub_person = { 'id': '198003', 'name': 'Software Heritage', 'email': 'robot@softwareheritage.org', } mock_service.lookup_person.return_value = stub_person # when rv = self.client.get('/api/1/person/198003/') # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, stub_person) @patch('swh.web.api.views.person.service') @istest def api_person_not_found(self, mock_service): # given mock_service.lookup_person.return_value = None # when rv = self.client.get('/api/1/person/666/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Person with id 666 not found.'}) diff --git a/swh/web/tests/api/views/test_release.py b/swh/web/tests/api/views/test_release.py index 8c74385c..f5169487 100644 --- a/swh/web/tests/api/views/test_release.py +++ b/swh/web/tests/api/views/test_release.py @@ -1,120 +1,121 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class ReleaseApiTestCase(SWHApiTestCase): +class ReleaseApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.release.service') @istest def api_release(self, mock_service): release_id = '7045404f3d1c54e6473' target_id = '6072557b6c10cd9a211' # given stub_release = { 'id': release_id, 'target_type': 'revision', 'target': target_id, "date": "Mon, 10 Mar 1997 08:00:00 GMT", "synthetic": True, 'author': { 'id': 10, 'name': 'author release name', 'email': 'author@email', }, } expected_release = { 'id': release_id, 'target_type': 'revision', 'target': target_id, 'target_url': '/api/1/revision/%s/' % target_id, "date": "Mon, 10 Mar 1997 08:00:00 GMT", "synthetic": True, 'author_url': '/api/1/person/10/', 'author': { 'id': 10, 'name': 'author release name', 'email': 'author@email', }, } mock_service.lookup_release.return_value = stub_release # when rv = self.client.get('/api/1/release/%s/' % release_id) # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_release) mock_service.lookup_release.assert_called_once_with(release_id) @patch('swh.web.api.views.release.service') @istest def api_release_target_type_not_a_revision(self, mock_service): release = '8d56a78' target = '9a5c3f' # given stub_release = { 'id': release, 'target_type': 'other-stuff', 'target': target, "date": "Mon, 10 Mar 1997 08:00:00 GMT", "synthetic": True, 'author': { 'id': 9, 'name': 'author release name', 'email': 'author@email', }, } expected_release = { 'id': release, 'target_type': 'other-stuff', 'target': target, "date": "Mon, 10 Mar 1997 08:00:00 GMT", "synthetic": True, 'author_url': '/api/1/person/9/', 'author': { 'id': 9, 'name': 'author release name', 'email': 'author@email', }, } mock_service.lookup_release.return_value = stub_release # when rv = self.client.get('/api/1/release/%s/' % release) # then self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_release) mock_service.lookup_release.assert_called_once_with(release) @patch('swh.web.api.views.release.service') @istest def api_release_not_found(self, mock_service): # given mock_service.lookup_release.return_value = None # when rv = self.client.get('/api/1/release/c54e6473c71bbb716529/') # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Release with sha1_git c54e6473c71bbb716529 not found.' }) diff --git a/swh/web/tests/api/views/test_revision.py b/swh/web/tests/api/views/test_revision.py index 05fc6896..19b48894 100644 --- a/swh/web/tests/api/views/test_revision.py +++ b/swh/web/tests/api/views/test_revision.py @@ -1,917 +1,920 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +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_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class ReleaseApiTestCase(SWHApiTestCase): +class ReleaseApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(expected_revision, rv.data) mock_service.lookup_revision.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'Revision with sha1_git 12345 not found.'}) @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/octet-stream') self.assertEquals(rv.content, b'synthetic revision message') mock_service.lookup_revision_message.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'NotFoundExc', 'reason': 'No message for revision'}) self.assertEquals mock_service.lookup_revision_message.assert_called_once_with( '18d8be353ed3480476f032475e7c233eff7371d5') @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 404) self.assertEquals(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', 'refs/heads/master', None) @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revision) mock_service.lookup_revision_by.assert_called_once_with( '1', 'refs/heads/master', None) @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(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.parse_timestamp') @patch('swh.web.api.views.revision.service') @patch('swh.web.api.views.revision.utils') @istest def api_revision_with_origin_and_branch_name_and_timestamp(self, mock_utils, mock_service, mock_parse_timestamp): # 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_parse_timestamp.return_value = 'parsed-date' 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revision) mock_service.lookup_revision_by.assert_called_once_with( '1', 'refs/origin/dev', 'parsed-date') mock_parse_timestamp.assert_called_once_with('1452591542') mock_utils.enrich_revision.assert_called_once_with( mock_revision) @patch('swh.web.api.views.revision.parse_timestamp') @patch('swh.web.api.views.revision.service') @patch('swh.web.api.views.revision.utils') @istest def api_revision_with_origin_and_branch_name_and_timestamp_with_escapes( self, mock_utils, mock_service, mock_parse_timestamp): 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_parse_timestamp.return_value = 'parsed-date' 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEqual(rv.data, expected_revision) mock_service.lookup_revision_by.assert_called_once_with( '1', 'refs/origin/dev', 'parsed-date') mock_parse_timestamp.assert_called_once_with( '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') @istest def 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') @istest def 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.assertEquals(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') @istest def 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.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 404) self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(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') @istest def api_revision_log_with_next(self, mock_service): # given stub_revisions = [] for i in range(27): stub_revisions.append({'id': i}) mock_service.lookup_revision_log.return_value = stub_revisions[:26] expected_revisions = [x for x in stub_revisions if 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_revisions) self.assertEquals(rv['Link'], '; rel="next"') mock_service.lookup_revision_log.assert_called_once_with( '8834ef7e7c357ce2af928115c6c6a42b7e2a44e6', 26) @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, expected_revisions) self.assertFalse(rv.has_header('Link')) mock_service.lookup_revision_log_by.assert_called_once_with( '1', 'refs/heads/master', None, 11) @patch('swh.web.api.views.revision.service') @istest def api_revision_log_by_with_next(self, mock_service): # given stub_revisions = [] for i in range(27): stub_revisions.append({'id': i}) mock_service.lookup_revision_log_by.return_value = stub_revisions[:26] expected_revisions = [x for x in stub_revisions if 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertIsNotNone(rv['Link']) self.assertEquals(rv.data, expected_revisions) mock_service.lookup_revision_log_by.assert_called_once_with( '1', 'refs/heads/master', None, 26) @patch('swh.web.api.views.revision.service') @istest def 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/') + print(rv.content) + # then self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertFalse(rv.has_header('Link')) self.assertEquals(rv.data, {'exception': 'NotFoundExc', 'reason': 'No revision'}) mock_service.lookup_revision_log_by.assert_called_once_with( '1', 'refs/heads/master', None, 11) @patch('swh.web.api.views.revision.service') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(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') @istest def 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.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, stub_content) mock_rev_dir.assert_called_once_with( {'sha1_git': '666'}, 'some/other/path', url, with_data=False) diff --git a/swh/web/tests/api/views/test_stat.py b/swh/web/tests/api/views/test_stat.py index 8afdba42..4a7b864d 100644 --- a/swh/web/tests/api/views/test_stat.py +++ b/swh/web/tests/api/views/test_stat.py @@ -1,96 +1,97 @@ # Copyright (C) 2015-2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch from swh.storage.exc import StorageDBError, StorageAPIError -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase -class StatApiTestCase(SWHApiTestCase): +class StatApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.stat.service') @istest def api_1_stat_counters_raise_error(self, mock_service): # given mock_service.stat_counters.side_effect = ValueError( 'voluntary error to check the bad request middleware.') # when rv = self.client.get('/api/1/stat/counters/') # then self.assertEquals(rv.status_code, 400) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'ValueError', 'reason': 'voluntary error to check the bad request middleware.'}) @patch('swh.web.api.views.stat.service') @istest def api_1_stat_counters_raise_swh_storage_error_db(self, mock_service): # given mock_service.stat_counters.side_effect = StorageDBError( 'SWH Storage exploded! Will be back online shortly!') # when rv = self.client.get('/api/1/stat/counters/') # then self.assertEquals(rv.status_code, 503) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'StorageDBError', 'reason': 'An unexpected error occurred in the backend: ' 'SWH Storage exploded! Will be back online shortly!'}) @patch('swh.web.api.views.stat.service') @istest def api_1_stat_counters_raise_swh_storage_error_api(self, mock_service): # given mock_service.stat_counters.side_effect = StorageAPIError( 'SWH Storage API dropped dead! Will resurrect from its ashes asap!' ) # when rv = self.client.get('/api/1/stat/counters/') # then self.assertEquals(rv.status_code, 503) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, { 'exception': 'StorageAPIError', 'reason': 'An unexpected error occurred in the api backend: ' 'SWH Storage API dropped dead! Will resurrect from its ashes asap!' }) @patch('swh.web.api.views.stat.service') @istest def api_1_stat_counters(self, mock_service): # given stub_stats = { "content": 1770830, "directory": 211683, "directory_entry_dir": 209167, "directory_entry_file": 1807094, "directory_entry_rev": 0, "entity": 0, "entity_history": 0, "occurrence": 0, "occurrence_history": 19600, "origin": 1096, "person": 0, "release": 8584, "revision": 7792, "revision_history": 0, "skipped_content": 0 } mock_service.stat_counters.return_value = stub_stats # when rv = self.client.get('/api/1/stat/counters/') self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, stub_stats) mock_service.stat_counters.assert_called_once_with() diff --git a/swh/web/tests/api/views/test_vault.py b/swh/web/tests/api/views/test_vault.py index 68302a21..3643220a 100644 --- a/swh/web/tests/api/views/test_vault.py +++ b/swh/web/tests/api/views/test_vault.py @@ -1,80 +1,81 @@ # Copyright (C) 2017 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 nose.tools import istest +from rest_framework.test import APITestCase from unittest.mock import patch from swh.model import hashutil -from ..swh_api_testcase import SWHApiTestCase +from swh.web.tests.testbase import SWHWebTestBase TEST_OBJ_ID = 'd4905454cc154b492bd6afed48694ae3c579345e' -class VaultApiTestCase(SWHApiTestCase): +class VaultApiTestCase(SWHWebTestBase, APITestCase): @patch('swh.web.api.views.vault.service') @istest def api_vault_cook(self, mock_service): stub_cook = { 'fetch_url': ('http://127.0.0.1:5004/api/1/vault/directory/{}/raw/' .format(TEST_OBJ_ID)), 'obj_id': 'd4905454cc154b492bd6afed48694ae3c579345e', 'obj_type': 'test_type', 'progress_message': None, 'status': 'done', 'task_uuid': 'de75c902-5ee5-4739-996e-448376a93eff', } stub_fetch = b'content' mock_service.vault_cook.return_value = stub_cook mock_service.vault_fetch.return_value = stub_fetch for obj_type in ('directory', 'revision_gitfast'): rv = self.client.get(('/api/1/vault/{}/{}/?email=test@test.mail') .format(obj_type, TEST_OBJ_ID)) self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data, stub_cook) mock_service.vault_cook.assert_called_with( obj_type, hashutil.hash_to_bytes(TEST_OBJ_ID), 'test@test.mail') rv = self.client.get(('/api/1/vault/{}/{}/raw/') .format(obj_type, TEST_OBJ_ID)) self.assertEquals(rv.status_code, 200) self.assertEquals(rv['Content-Type'], 'application/gzip') self.assertEquals(rv.content, stub_fetch) mock_service.vault_fetch.assert_called_with( obj_type, hashutil.hash_to_bytes(TEST_OBJ_ID)) @patch('swh.web.api.views.vault.service') @istest def api_vault_cook_notfound(self, mock_service): mock_service.vault_cook.return_value = None mock_service.vault_fetch.return_value = None for obj_type in ('directory', 'revision_gitfast'): rv = self.client.get(('/api/1/vault/{}/{}/') .format(obj_type, TEST_OBJ_ID)) self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data['exception'], 'NotFoundExc') mock_service.vault_cook.assert_called_with( obj_type, hashutil.hash_to_bytes(TEST_OBJ_ID), None) rv = self.client.get(('/api/1/vault/{}/{}/raw/') .format(obj_type, TEST_OBJ_ID)) self.assertEquals(rv.status_code, 404) self.assertEquals(rv['Content-Type'], 'application/json') self.assertEquals(rv.data['exception'], 'NotFoundExc') mock_service.vault_fetch.assert_called_with( obj_type, hashutil.hash_to_bytes(TEST_OBJ_ID)) diff --git a/swh/web/tests/browse/test_utils.py b/swh/web/tests/browse/test_utils.py index 0e242fb9..d6be5690 100644 --- a/swh/web/tests/browse/test_utils.py +++ b/swh/web/tests/browse/test_utils.py @@ -1,298 +1,299 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import unittest from unittest.mock import patch from nose.tools import istest from swh.web.browse import utils from swh.web.common.utils import reverse +from swh.web.tests.testbase import SWHWebTestBase from .views.data.revision_test_data import revision_history_log_test -class SwhBrowseUtilsTestCase(unittest.TestCase): +class SwhBrowseUtilsTestCase(SWHWebTestBase, unittest.TestCase): @istest def gen_path_info(self): input_path = '/home/user/swh-environment/swh-web/' expected_result = [ {'name': 'home', 'path': 'home'}, {'name': 'user', 'path': 'home/user'}, {'name': 'swh-environment', 'path': 'home/user/swh-environment'}, {'name': 'swh-web', 'path': 'home/user/swh-environment/swh-web'} ] path_info = utils.gen_path_info(input_path) self.assertEquals(path_info, expected_result) input_path = 'home/user/swh-environment/swh-web' path_info = utils.gen_path_info(input_path) self.assertEquals(path_info, expected_result) @istest def get_mimetype_and_encoding_for_content(self): text = b'Hello world!' self.assertEqual(utils.get_mimetype_and_encoding_for_content(text), ('text/plain', 'us-ascii')) @patch('swh.web.browse.utils.service') @istest def get_origin_visits(self, mock_service): mock_service.MAX_LIMIT = 2 def _lookup_origin_visits(*args, **kwargs): if kwargs['last_visit'] is None: return [{'visit': 1}, {'visit': 2}] else: return [{'visit': 3}] mock_service.lookup_origin_visits.side_effect = _lookup_origin_visits origin_visits = utils.get_origin_visits(1) self.assertEqual(len(origin_visits), 3) @patch('swh.web.browse.utils.service') @istest def test_get_origin_visit_branches(self, mock_service): mock_service.lookup_origin_visit.return_value = \ {'date': '2015-08-04T22:26:14.804009+00:00', 'metadata': {}, 'occurrences': { 'refs/heads/master': { 'target': '9fbd21adbac36be869514e82e2e98505dc47219c', 'target_type': 'revision', 'target_url': '/api/1/revision/9fbd21adbac36be869514e82e2e98505dc47219c/' # noqa }, 'refs/tags/0.10.0': { 'target': '6072557b6c10cd9a21145781e26ad1f978ed14b9', 'target_type': 'revision', 'target_url': '/api/1/revision/6072557b6c10cd9a21145781e26ad1f978ed14b9/' # noqa }, 'refs/tags/0.10.1': { 'target': 'ecc003b43433e5b46511157598e4857a761007bf', 'target_type': 'revision', 'target_url': '/api/1/revision/ecc003b43433e5b46511157598e4857a761007bf/' # noqa } }, 'origin': 1, 'origin_url': '/api/1/origin/1/', 'status': 'full', 'visit': 1} mock_service.lookup_revision_multiple.return_value = \ [{'directory': '828da2b80e41aa958b2c98526f4a1d2cc7d298b7'}, {'directory': '2df4cd84ecc65b50b1d5318d3727e02a39b8a4cf'}, {'directory': '28ba64f97ef709e54838ae482c2da2619a74a0bd'}] expected_result = [ {'name': 'refs/heads/master', 'revision': '9fbd21adbac36be869514e82e2e98505dc47219c', 'directory': '828da2b80e41aa958b2c98526f4a1d2cc7d298b7'}, {'name': 'refs/tags/0.10.0', 'revision': '6072557b6c10cd9a21145781e26ad1f978ed14b9', 'directory': '2df4cd84ecc65b50b1d5318d3727e02a39b8a4cf'}, {'name': 'refs/tags/0.10.1', 'revision': 'ecc003b43433e5b46511157598e4857a761007bf', 'directory': '28ba64f97ef709e54838ae482c2da2619a74a0bd'} ] origin_visit_branches = utils.get_origin_visit_branches(1, 1) self.assertEqual(origin_visit_branches, expected_result) @istest def gen_link(self): self.assertEqual(utils.gen_link('https://www.softwareheritage.org/', 'SWH'), # noqa 'SWH') @istest def gen_person_link(self): person_id = 8221896 person_name = 'Antoine Lambert' person_url = reverse('browse-person', kwargs={'person_id': person_id}) self.assertEqual(utils.gen_person_link(person_id, person_name), '%s' % (person_url, person_name)) @istest def gen_revision_link(self): revision_id = '28a0bc4120d38a394499382ba21d6965a67a3703' revision_url = reverse('browse-revision', kwargs={'sha1_git': revision_id}) self.assertEqual(utils.gen_revision_link(revision_id), '%s' % (revision_url, revision_id)) self.assertEqual(utils.gen_revision_link(revision_id, shorten_id=True), '%s' % (revision_url, revision_id[:7])) # noqa @istest def test_prepare_revision_log_for_display_no_contex(self): per_page = 10 first_page_logs_data = revision_history_log_test[:per_page+1] second_page_logs_data = revision_history_log_test[per_page:2*per_page+1] # noqa third_page_logs_data = revision_history_log_test[2*per_page:3*per_page+1] # noqa last_page_logs_data = revision_history_log_test[3*per_page:3*per_page+5] # noqa revision_log_display_data = utils.prepare_revision_log_for_display( first_page_logs_data, per_page, None) self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(first_page_logs_data, per_page)) self.assertEqual(revision_log_display_data['prev_rev'], first_page_logs_data[-1]['id']) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], first_page_logs_data[0]['id']) self.assertEqual(revision_log_display_data['next_rev'], None) self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], None) old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) # noqa revision_log_display_data = utils.prepare_revision_log_for_display( second_page_logs_data, per_page, old_prev_revs_bc) self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(second_page_logs_data, per_page)) self.assertEqual(revision_log_display_data['prev_rev'], second_page_logs_data[-1]['id']) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], old_prev_revs_bc + '/' + second_page_logs_data[0]['id']) # noqa self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc) self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], None) old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) # noqa revision_log_display_data = utils.prepare_revision_log_for_display( third_page_logs_data, per_page, old_prev_revs_bc) self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(third_page_logs_data, per_page)) # noqa self.assertEqual(revision_log_display_data['prev_rev'], third_page_logs_data[-1]['id']) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], old_prev_revs_bc + '/' + third_page_logs_data[0]['id']) # noqa self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc.split('/')[-1]) self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], '/'.join(old_prev_revs_bc.split('/')[:-1])) old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) # noqa revision_log_display_data = utils.prepare_revision_log_for_display( last_page_logs_data, per_page, old_prev_revs_bc) self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(last_page_logs_data, per_page)) # noqa self.assertEqual(revision_log_display_data['prev_rev'], None) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], None) # noqa self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc.split('/')[-1]) # noqa self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], '/'.join(old_prev_revs_bc.split('/')[:-1])) @istest def test_prepare_revision_log_for_display_origin_contex(self): per_page = 10 first_page_logs_data = revision_history_log_test[:per_page+1] second_page_logs_data = revision_history_log_test[per_page:2*per_page+1] # noqa third_page_logs_data = revision_history_log_test[2*per_page:3*per_page+1] # noqa last_page_logs_data = revision_history_log_test[3*per_page:3*per_page+5] # noqa revision_log_display_data = utils.prepare_revision_log_for_display( first_page_logs_data, per_page, None, origin_context=True) self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(first_page_logs_data, per_page)) self.assertEqual(revision_log_display_data['prev_rev'], first_page_logs_data[-1]['id']) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], first_page_logs_data[-1]['id']) self.assertEqual(revision_log_display_data['next_rev'], None) self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], None) old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) # noqa revision_log_display_data = utils.prepare_revision_log_for_display( second_page_logs_data, per_page, old_prev_revs_bc, origin_context=True) # noqa self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(second_page_logs_data, per_page)) self.assertEqual(revision_log_display_data['prev_rev'], second_page_logs_data[-1]['id']) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], old_prev_revs_bc + '/' + second_page_logs_data[-1]['id']) # noqa self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc) self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], None) old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) # noqa revision_log_display_data = utils.prepare_revision_log_for_display( third_page_logs_data, per_page, old_prev_revs_bc, origin_context=True) # noqa self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(third_page_logs_data, per_page)) # noqa self.assertEqual(revision_log_display_data['prev_rev'], third_page_logs_data[-1]['id']) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], old_prev_revs_bc + '/' + third_page_logs_data[-1]['id']) # noqa self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc.split('/')[-1]) self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], '/'.join(old_prev_revs_bc.split('/')[:-1])) old_prev_revs_bc = str(revision_log_display_data['prev_revs_breadcrumb']) # noqa revision_log_display_data = utils.prepare_revision_log_for_display( last_page_logs_data, per_page, old_prev_revs_bc, origin_context=True) # noqa self.assertEqual(revision_log_display_data['revision_log_data'], utils._format_log_entries(last_page_logs_data, per_page)) # noqa self.assertEqual(revision_log_display_data['prev_rev'], None) self.assertEqual(revision_log_display_data['prev_revs_breadcrumb'], None) # noqa self.assertEqual(revision_log_display_data['next_rev'], old_prev_revs_bc.split('/')[-1]) # noqa self.assertEqual(revision_log_display_data['next_revs_breadcrumb'], '/'.join(old_prev_revs_bc.split('/')[:-1])) diff --git a/swh/web/tests/browse/views/test_content.py b/swh/web/tests/browse/views/test_content.py index 4bce0b70..cd6c3aa1 100644 --- a/swh/web/tests/browse/views/test_content.py +++ b/swh/web/tests/browse/views/test_content.py @@ -1,246 +1,246 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information import base64 from unittest.mock import patch from nose.tools import istest from django.test import TestCase from django.utils.html import escape from django.utils.encoding import DjangoUnicodeDecodeError from swh.web.common.exc import NotFoundExc from swh.web.common.utils import reverse -from swh.web.browse.utils import ( - gen_path_info -) +from swh.web.browse.utils import gen_path_info +from swh.web.tests.testbase import SWHWebTestBase + from .data.content_test_data import ( stub_content_text_data, stub_content_text_path_with_root_dir, stub_content_bin_data, stub_content_bin_filename, stub_content_text_no_highlight_data, non_utf8_encoded_content_data, non_utf8_encoded_content, non_utf8_encoding ) -class SwhBrowseContentTest(TestCase): +class SwhBrowseContentTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.content.request_content') @istest def content_view_text(self, mock_request_content): mock_request_content.return_value = stub_content_text_data url = reverse('browse-content', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('content.html') self.assertContains(resp, '') self.assertContains(resp, escape(stub_content_text_data['raw_data'])) self.assertContains(resp, url_raw) @patch('swh.web.browse.views.content.request_content') @istest def content_view_text_no_highlight(self, mock_request_content): mock_request_content.return_value = stub_content_text_no_highlight_data url = reverse('browse-content', kwargs={'query_string': stub_content_text_no_highlight_data['checksums']['sha1']}) # noqa url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_no_highlight_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('content.html') self.assertContains(resp, '') self.assertContains(resp, escape(stub_content_text_no_highlight_data['raw_data'])) # noqa self.assertContains(resp, url_raw) @patch('swh.web.browse.utils.service') @istest def content_view_no_utf8_text(self, mock_service): mock_service.lookup_content.return_value = \ non_utf8_encoded_content_data mock_service.lookup_content_raw.return_value = \ {'data': non_utf8_encoded_content} mock_service.lookup_content_filetype.return_value = None mock_service.lookup_content_language.return_value = None mock_service.lookup_content_license.return_value = None url = reverse('browse-content', kwargs={'query_string': non_utf8_encoded_content_data['checksums']['sha1']}) # noqa try: resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('content.html') self.assertContains(resp, escape(non_utf8_encoded_content.decode(non_utf8_encoding).encode('utf-8'))) # noqa except DjangoUnicodeDecodeError: self.fail('Textual content is not encoded in utf-8') @patch('swh.web.browse.views.content.request_content') @istest def content_view_image(self, mock_request_content): mime_type = 'image/png' mock_request_content.return_value = stub_content_bin_data url = reverse('browse-content', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}) # noqa url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('content.html') pngEncoded = base64.b64encode(stub_content_bin_data['raw_data']) \ .decode('utf-8') self.assertContains(resp, '' % (mime_type, pngEncoded)) self.assertContains(resp, url_raw) @patch('swh.web.browse.views.content.request_content') @istest def content_view_with_path(self, mock_request_content): mock_request_content.return_value = stub_content_text_data url = reverse('browse-content', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}, # noqa query_params={'path': stub_content_text_path_with_root_dir}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('content.html') self.assertContains(resp, '') self.assertContains(resp, escape(stub_content_text_data['raw_data'])) split_path = stub_content_text_path_with_root_dir.split('/') root_dir_sha1 = split_path[0] filename = split_path[-1] path = stub_content_text_path_with_root_dir \ .replace(root_dir_sha1 + '/', '') \ .replace(filename, '') path_info = gen_path_info(path) root_dir_url = reverse('browse-directory', kwargs={'sha1_git': root_dir_sha1}) self.assertContains(resp, '
  • ', count=len(path_info)+1) self.assertContains(resp, '' + root_dir_sha1[:7] + '') for p in path_info: dir_url = reverse('browse-directory', kwargs={'sha1_git': root_dir_sha1, 'path': p['path']}) self.assertContains(resp, '' + p['name'] + '') self.assertContains(resp, '
  • ' + filename + '
  • ') url_raw = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}, # noqa query_params={'filename': filename}) self.assertContains(resp, url_raw) @patch('swh.web.browse.views.content.request_content') @istest def test_content_raw_text(self, mock_request_content): mock_request_content.return_value = stub_content_text_data url = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'text/plain') self.assertEqual(resp['Content-disposition'], 'filename=%s_%s' % ('sha1', stub_content_text_data['checksums']['sha1'])) # noqa self.assertEqual(resp.content, stub_content_text_data['raw_data']) filename = stub_content_text_path_with_root_dir.split('/')[-1] url = reverse('browse-content-raw', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}, # noqa query_params={'filename': filename}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'text/plain') self.assertEqual(resp['Content-disposition'], 'filename=%s' % filename) self.assertEqual(resp.content, stub_content_text_data['raw_data']) @patch('swh.web.browse.views.content.request_content') @istest def content_raw_bin(self, mock_request_content): mock_request_content.return_value = stub_content_bin_data url = reverse('browse-content-raw', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'application/octet-stream') self.assertEqual(resp['Content-disposition'], 'attachment; filename=%s_%s' % ('sha1', stub_content_bin_data['checksums']['sha1'])) self.assertEqual(resp.content, stub_content_bin_data['raw_data']) url = reverse('browse-content-raw', kwargs={'query_string': stub_content_bin_data['checksums']['sha1']}, # noqa query_params={'filename': stub_content_bin_filename}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertEqual(resp['Content-Type'], 'application/octet-stream') self.assertEqual(resp['Content-disposition'], 'attachment; filename=%s' % stub_content_bin_filename) self.assertEqual(resp.content, stub_content_bin_data['raw_data']) @patch('swh.web.browse.views.content.request_content') @istest def content_request_errors(self, mock_request_content): url = reverse('browse-content', kwargs={'query_string': '123456'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 400) self.assertTemplateUsed('error.html') mock_request_content.side_effect = NotFoundExc('content not found') url = reverse('browse-content', kwargs={'query_string': stub_content_text_data['checksums']['sha1']}) # noqa resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') diff --git a/swh/web/tests/browse/views/test_directory.py b/swh/web/tests/browse/views/test_directory.py index 68f45b1d..bd96ae29 100644 --- a/swh/web/tests/browse/views/test_directory.py +++ b/swh/web/tests/browse/views/test_directory.py @@ -1,126 +1,128 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from unittest.mock import patch from nose.tools import istest, nottest from django.test import TestCase from swh.web.common.exc import BadInputExc, NotFoundExc from swh.web.common.utils import reverse from swh.web.browse.utils import gen_path_info +from swh.web.tests.testbase import SWHWebTestBase + from .data.directory_test_data import ( stub_root_directory_sha1, stub_root_directory_data, stub_sub_directory_path, stub_sub_directory_data ) -class SwhBrowseDirectoryTest(TestCase): +class SwhBrowseDirectoryTest(SWHWebTestBase, TestCase): @nottest def directory_view(self, root_directory_sha1, directory_entries, path=None): dirs = [e for e in directory_entries if e['type'] == 'dir'] files = [e for e in directory_entries if e['type'] == 'file'] url_args = {'sha1_git': root_directory_sha1} if path: url_args['path'] = path url = reverse('browse-directory', kwargs=url_args) root_dir_url = reverse('browse-directory', kwargs={'sha1_git': root_directory_sha1}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('directory.html') self.assertContains(resp, '' + root_directory_sha1[:7] + '') self.assertContains(resp, '', count=len(dirs)) self.assertContains(resp, '', count=len(files)) for d in dirs: dir_path = d['name'] if path: dir_path = "%s/%s" % (path, d['name']) dir_url = reverse('browse-directory', kwargs={'sha1_git': root_directory_sha1, 'path': dir_path}) self.assertContains(resp, dir_url) for f in files: file_path = "%s/%s" % (root_directory_sha1, f['name']) if path: file_path = "%s/%s/%s" % (root_directory_sha1, path, f['name']) query_string = 'sha1_git:' + f['target'] file_url = reverse('browse-content', kwargs={'query_string': query_string}, query_params={'path': file_path}) self.assertContains(resp, file_url) path_info = gen_path_info(path) self.assertContains(resp, '
  • ', count=len(path_info)+1) self.assertContains(resp, '%s' % (root_dir_url, root_directory_sha1[:7])) for p in path_info: dir_url = reverse('browse-directory', kwargs={'sha1_git': root_directory_sha1, 'path': p['path']}) self.assertContains(resp, '%s' % (dir_url, p['name'])) @patch('swh.web.browse.utils.service') @istest def root_directory_view(self, mock_service): mock_service.lookup_directory.return_value = \ stub_root_directory_data self.directory_view(stub_root_directory_sha1, stub_root_directory_data) @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.directory.service') @istest def sub_directory_view(self, mock_directory_service, mock_utils_service): mock_utils_service.lookup_directory.return_value = \ stub_sub_directory_data mock_directory_service.lookup_directory_with_path.return_value = \ {'target': '120c39eeb566c66a77ce0e904d29dfde42228adc'} self.directory_view(stub_root_directory_sha1, stub_sub_directory_data, stub_sub_directory_path) @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.directory.service') @istest def directory_request_errors(self, mock_directory_service, mock_utils_service): mock_utils_service.lookup_directory.side_effect = \ BadInputExc('directory not found') dir_url = reverse('browse-directory', kwargs={'sha1_git': '1253456'}) resp = self.client.get(dir_url) self.assertEquals(resp.status_code, 400) self.assertTemplateUsed('error.html') mock_utils_service.lookup_directory.side_effect = \ NotFoundExc('directory not found') dir_url = reverse('browse-directory', kwargs={'sha1_git': '1253456'}) resp = self.client.get(dir_url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') diff --git a/swh/web/tests/browse/views/test_origin.py b/swh/web/tests/browse/views/test_origin.py index 3714a314..6f0d749c 100644 --- a/swh/web/tests/browse/views/test_origin.py +++ b/swh/web/tests/browse/views/test_origin.py @@ -1,499 +1,500 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from unittest.mock import patch from nose.tools import istest, nottest from django.test import TestCase from django.utils.html import escape from swh.web.common.exc import NotFoundExc from swh.web.common.utils import reverse +from swh.web.tests.testbase import SWHWebTestBase from .data.origin_test_data import ( origin_info_test_data, origin_visits_test_data, stub_content_origin_id, stub_content_origin_visit_id, stub_content_origin_visit_unix_ts, stub_content_origin_visit_iso_date, stub_content_origin_branch, stub_content_origin_visits, stub_content_origin_branches, stub_origin_id, stub_visit_id, stub_origin_visits, stub_origin_branches, stub_origin_root_directory_entries, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_sub_directory_path, stub_origin_sub_directory_entries, stub_visit_unix_ts, stub_visit_iso_date ) from .data.content_test_data import ( stub_content_root_dir, stub_content_text_data, stub_content_text_path ) from swh.web.browse.utils import ( gen_path_info ) -class SwhBrowseOriginTest(TestCase): +class SwhBrowseOriginTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.origin.get_origin_visits') @patch('swh.web.browse.views.origin.service') @istest def test_origin_browse(self, mock_service, mock_get_origin_visits): mock_service.lookup_origin.return_value = origin_info_test_data mock_get_origin_visits.return_value = origin_visits_test_data url = reverse('browse-origin', kwargs={'origin_id': origin_info_test_data['id']}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('origin.html') self.assertContains(resp, '%s' % origin_info_test_data['id']) self.assertContains(resp, '
    %s
    ' % origin_info_test_data['type']) # noqa self.assertContains(resp, '
    %s
    ' % (origin_info_test_data['url'], origin_info_test_data['url'])) self.assertContains(resp, '', count=len(origin_visits_test_data)) for visit in origin_visits_test_data: browse_url = reverse('browse-origin-directory', kwargs={'origin_id': visit['origin'], 'visit_id': visit['visit']}) self.assertContains(resp, '%s' % (browse_url, browse_url)) @nottest def origin_content_view_test(self, origin_id, origin_visits, origin_branches, origin_branch, root_dir_sha1, content_sha1, content_path, content_data, content_language, visit_id=None, ts=None): url_args = {'origin_id': origin_id, 'path': content_path} if not visit_id: visit_id = origin_visits[-1]['visit'] if ts: url_args['timestamp'] = ts else: url_args['visit_id'] = visit_id url = reverse('browse-origin-content', kwargs=url_args) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('content.html') self.assertContains(resp, '' % content_language) self.assertContains(resp, escape(content_data)) split_path = content_path.split('/') filename = split_path[-1] path = content_path.replace(filename, '')[:-1] path_info = gen_path_info(path) del url_args['path'] root_dir_url = reverse('browse-origin-directory', kwargs=url_args, query_params={'branch': origin_branch}) self.assertContains(resp, '
  • ', count=len(path_info)+1) self.assertContains(resp, '%s' % (root_dir_url, root_dir_sha1[:7])) for p in path_info: url_args['path'] = p['path'] dir_url = reverse('browse-origin-directory', kwargs=url_args, query_params={'branch': origin_branch}) self.assertContains(resp, '%s' % (dir_url, p['name'])) self.assertContains(resp, '
  • %s
  • ' % filename) query_string = 'sha1_git:' + content_sha1 url_raw = reverse('browse-content-raw', kwargs={'query_string': query_string}, query_params={'filename': filename}) self.assertContains(resp, url_raw) self.assertContains(resp, '
  • ', count=len(origin_branches)) url_args['path'] = content_path for branch in origin_branches: root_dir_branch_url = \ reverse('browse-origin-content', kwargs=url_args, query_params={'branch': branch['name']}) self.assertContains(resp, '%s' % (root_dir_branch_url, branch['name'])) @patch('swh.web.browse.views.origin.get_origin_visits') @patch('swh.web.browse.views.origin.get_origin_visit_branches') @patch('swh.web.browse.views.origin.service') @patch('swh.web.browse.views.origin.request_content') @istest def origin_content_view(self, mock_request_content, mock_service, mock_get_origin_visit_branches, mock_get_origin_visits): stub_content_text_sha1 = stub_content_text_data['checksums']['sha1'] mock_get_origin_visits.return_value = stub_content_origin_visits mock_get_origin_visit_branches.return_value = stub_content_origin_branches # noqa mock_service.lookup_directory_with_path.return_value = \ {'target': stub_content_text_sha1} mock_request_content.return_value = stub_content_text_data self.origin_content_view_test(stub_content_origin_id, stub_content_origin_visits, stub_content_origin_branches, stub_content_origin_branch, stub_content_root_dir, stub_content_text_sha1, stub_content_text_path, stub_content_text_data['raw_data'], 'cpp') self.origin_content_view_test(stub_content_origin_id, stub_content_origin_visits, stub_content_origin_branches, stub_content_origin_branch, stub_content_root_dir, stub_content_text_sha1, stub_content_text_path, stub_content_text_data['raw_data'], 'cpp', visit_id=stub_content_origin_visit_id) self.origin_content_view_test(stub_content_origin_id, stub_content_origin_visits, stub_content_origin_branches, stub_content_origin_branch, stub_content_root_dir, stub_content_text_sha1, stub_content_text_path, stub_content_text_data['raw_data'], 'cpp', ts=stub_content_origin_visit_unix_ts) self.origin_content_view_test(stub_content_origin_id, stub_content_origin_visits, stub_content_origin_branches, stub_content_origin_branch, stub_content_root_dir, stub_content_text_sha1, stub_content_text_path, stub_content_text_data['raw_data'], 'cpp', ts=stub_content_origin_visit_iso_date) @nottest def origin_directory_view(self, origin_id, origin_visits, origin_branches, origin_branch, root_directory_sha1, directory_entries, visit_id=None, ts=None, path=None): dirs = [e for e in directory_entries if e['type'] == 'dir'] files = [e for e in directory_entries if e['type'] == 'file'] if not visit_id: visit_id = origin_visits[-1]['visit'] url_args = {'origin_id': origin_id} if ts: url_args['timestamp'] = ts else: url_args['visit_id'] = visit_id if path: url_args['path'] = path url = reverse('browse-origin-directory', kwargs=url_args) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('directory.html') self.assertContains(resp, '', count=len(dirs)) self.assertContains(resp, '', count=len(files)) for d in dirs: dir_path = d['name'] if path: dir_path = "%s/%s" % (path, d['name']) dir_url_args = dict(url_args) dir_url_args['path'] = dir_path dir_url = reverse('browse-origin-directory', kwargs=dir_url_args, query_params={'branch': origin_branch}) # noqa self.assertContains(resp, dir_url) for f in files: file_path = f['name'] if path: file_path = "%s/%s" % (path, f['name']) file_url_args = dict(url_args) file_url_args['path'] = file_path file_url = reverse('browse-origin-content', kwargs=file_url_args, query_params={'branch': origin_branch}) # noqa self.assertContains(resp, file_url) if 'path' in url_args: del url_args['path'] root_dir_branch_url = \ reverse('browse-origin-directory', kwargs=url_args, query_params={'branch': origin_branch}) nb_bc_paths = 1 if path: nb_bc_paths = len(path.split('/')) + 1 self.assertContains(resp, '
  • ', count=nb_bc_paths) self.assertContains(resp, '%s' % (root_dir_branch_url, root_directory_sha1[:7])) self.assertContains(resp, '
  • ', count=len(origin_branches)) if path: url_args['path'] = path for branch in origin_branches: root_dir_branch_url = \ reverse('browse-origin-directory', kwargs=url_args, query_params={'branch': branch['name']}) self.assertContains(resp, '%s' % (root_dir_branch_url, branch['name'])) @patch('swh.web.browse.views.origin.get_origin_visits') @patch('swh.web.browse.views.origin.get_origin_visit_branches') @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.origin.service') @istest def origin_root_directory_view(self, mock_origin_service, mock_utils_service, mock_get_origin_visit_branches, mock_get_origin_visits): mock_get_origin_visits.return_value = stub_origin_visits mock_get_origin_visit_branches.return_value = stub_origin_branches mock_utils_service.lookup_directory.return_value = \ stub_origin_root_directory_entries mock_origin_service.lookup_origin.return_value = origin_info_test_data self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_root_directory_entries) self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_root_directory_entries, visit_id=stub_visit_id) self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_root_directory_entries, ts=stub_visit_unix_ts) self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_root_directory_entries, ts=stub_visit_iso_date) @patch('swh.web.browse.views.origin.get_origin_visits') @patch('swh.web.browse.views.origin.get_origin_visit_branches') @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.origin.service') @istest def origin_sub_directory_view(self, mock_origin_service, mock_utils_service, mock_get_origin_visit_branches, mock_get_origin_visits): mock_get_origin_visits.return_value = stub_origin_visits mock_get_origin_visit_branches.return_value = stub_origin_branches mock_utils_service.lookup_directory.return_value = \ stub_origin_sub_directory_entries mock_origin_service.lookup_directory_with_path.return_value = \ {'target': '120c39eeb566c66a77ce0e904d29dfde42228adb'} self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_sub_directory_entries, path=stub_origin_sub_directory_path) self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_sub_directory_entries, visit_id=stub_visit_id, path=stub_origin_sub_directory_path) self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_sub_directory_entries, ts=stub_visit_unix_ts, path=stub_origin_sub_directory_path) self.origin_directory_view(stub_origin_id, stub_origin_visits, stub_origin_branches, stub_origin_master_branch, stub_origin_root_directory_sha1, stub_origin_sub_directory_entries, ts=stub_visit_iso_date, path=stub_origin_sub_directory_path) @patch('swh.web.browse.views.origin.request_content') @patch('swh.web.browse.views.origin.get_origin_visits') @patch('swh.web.browse.views.origin.get_origin_visit_branches') @patch('swh.web.browse.utils.service') @patch('swh.web.browse.views.origin.service') @istest def origin_request_errors(self, mock_origin_service, mock_utils_service, mock_get_origin_visit_branches, mock_get_origin_visits, mock_request_content): mock_origin_service.lookup_origin.side_effect = \ NotFoundExc('origin not found') url = reverse('browse-origin', kwargs={'origin_id': '1'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, "origin not found", status_code=404) mock_origin_service.lookup_origin.side_effect = None mock_origin_service.lookup_origin.return_value = origin_info_test_data mock_get_origin_visits.return_value = [] url = reverse('browse-origin-directory', kwargs={'origin_id': '1'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, "No SWH visit", status_code=404) mock_get_origin_visits.return_value = stub_origin_visits mock_get_origin_visit_branches.side_effect = \ NotFoundExc('visit not found') url = reverse('browse-origin-directory', kwargs={'origin_id': '1', 'visit_id': len(stub_origin_visits)+1}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'visit not found', status_code=404) mock_get_origin_visits.return_value = stub_origin_visits mock_get_origin_visit_branches.side_effect = None mock_get_origin_visit_branches.return_value = [] url = reverse('browse-origin-directory', kwargs={'origin_id': '1'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertRegex(resp.content.decode('utf-8'), 'Branch HEAD.*not found') mock_get_origin_visit_branches.return_value = stub_origin_branches mock_utils_service.lookup_directory.side_effect = \ NotFoundExc('Directory not found') url = reverse('browse-origin-directory', kwargs={'origin_id': '1'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Directory not found', status_code=404) mock_origin_service.lookup_origin.side_effect = None mock_origin_service.lookup_origin.return_value = origin_info_test_data mock_get_origin_visits.return_value = [] url = reverse('browse-origin-content', kwargs={'origin_id': '1', 'path': 'foo'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, "No SWH visit", status_code=404) mock_get_origin_visits.return_value = stub_origin_visits mock_get_origin_visit_branches.side_effect = \ NotFoundExc('visit not found') url = reverse('browse-origin-content', kwargs={'origin_id': '1', 'visit_id': len(stub_origin_visits)+1, 'path': 'foo'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'visit not found', status_code=404) mock_get_origin_visits.return_value = stub_origin_visits mock_get_origin_visit_branches.side_effect = None mock_get_origin_visit_branches.return_value = [] url = reverse('browse-origin-content', kwargs={'origin_id': '1', 'path': 'foo'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertRegex(resp.content.decode('utf-8'), 'Branch HEAD.*not found') mock_get_origin_visit_branches.return_value = stub_origin_branches mock_origin_service.lookup_directory_with_path.return_value = \ {'target': stub_content_text_data['checksums']['sha1']} mock_request_content.side_effect = \ NotFoundExc('Content not found') url = reverse('browse-origin-content', kwargs={'origin_id': '1', 'path': 'foo'}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Content not found', status_code=404) diff --git a/swh/web/tests/browse/views/test_person.py b/swh/web/tests/browse/views/test_person.py index 416c0365..e031fff6 100644 --- a/swh/web/tests/browse/views/test_person.py +++ b/swh/web/tests/browse/views/test_person.py @@ -1,55 +1,56 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from unittest.mock import patch from nose.tools import istest from django.test import TestCase from swh.web.common.exc import NotFoundExc from swh.web.common.utils import reverse +from swh.web.tests.testbase import SWHWebTestBase -class SwhBrowsePersonTest(TestCase): +class SwhBrowsePersonTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.person.service') @istest def person_browse(self, mock_service): test_person_data = \ { "email": "j.adams440@gmail.com", "fullname": "oysterCrusher ", "id": 457587, "name": "oysterCrusher" } mock_service.lookup_person.return_value = test_person_data url = reverse('browse-person', kwargs={'person_id': 457587}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('person.html') self.assertContains(resp, '
    %s
    ' % test_person_data['id']) self.assertContains(resp, '
    %s
    ' % test_person_data['name']) self.assertContains(resp, '
    %s
    ' % (test_person_data['email'], test_person_data['email'])) self.assertContains(resp, '
    %s <%s>
    ' % # noqa (test_person_data['name'], test_person_data['email'], test_person_data['email'])) @patch('swh.web.browse.views.person.service') @istest def person_request_error(self, mock_service): mock_service.lookup_person.side_effect = \ NotFoundExc('Person not found') url = reverse('browse-person', kwargs={'person_id': 457587}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Person not found', status_code=404) diff --git a/swh/web/tests/browse/views/test_revision.py b/swh/web/tests/browse/views/test_revision.py index cf8debed..430548b0 100644 --- a/swh/web/tests/browse/views/test_revision.py +++ b/swh/web/tests/browse/views/test_revision.py @@ -1,199 +1,200 @@ # Copyright (C) 2017 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from unittest.mock import patch from nose.tools import istest from django.test import TestCase from django.utils.html import escape from swh.web.common.exc import NotFoundExc from swh.web.common.utils import reverse, format_utc_iso_date +from swh.web.tests.testbase import SWHWebTestBase from .data.revision_test_data import ( revision_id_test, revision_metadata_test, revision_history_log_test ) -class SwhBrowseRevisionTest(TestCase): +class SwhBrowseRevisionTest(SWHWebTestBase, TestCase): @patch('swh.web.browse.views.revision.service') @istest def revision_browse(self, mock_service): mock_service.lookup_revision.return_value = revision_metadata_test url = reverse('browse-revision', kwargs={'sha1_git': revision_id_test}) author_id = revision_metadata_test['author']['id'] author_name = revision_metadata_test['author']['name'] committer_id = revision_metadata_test['committer']['id'] committer_name = revision_metadata_test['committer']['name'] dir_id = revision_metadata_test['directory'] author_url = reverse('browse-person', kwargs={'person_id': author_id}) committer_url = reverse('browse-person', kwargs={'person_id': committer_id}) directory_url = reverse('browse-directory', kwargs={'sha1_git': dir_id}) history_url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}) resp = self.client.get(url) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('revision.html') self.assertContains(resp, '%s' % (author_url, author_name)) self.assertContains(resp, '%s' % (committer_url, committer_name)) self.assertContains(resp, '%s' % (directory_url, dir_id)) self.assertContains(resp, '%s' % (history_url, history_url)) for parent in revision_metadata_test['parents']: parent_url = reverse('browse-revision', kwargs={'sha1_git': parent}) self.assertContains(resp, '%s' % (parent_url, parent)) author_date = revision_metadata_test['date'] committer_date = revision_metadata_test['committer_date'] message = revision_metadata_test['message'] self.assertContains(resp, format_utc_iso_date(author_date)) self.assertContains(resp, format_utc_iso_date(committer_date)) self.assertContains(resp, message) @patch('swh.web.browse.views.revision.service') @istest def revision_log_browse(self, mock_service): per_page = 10 mock_service.lookup_revision_log.return_value = \ revision_history_log_test[:per_page+1] url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}, query_params={'per_page': per_page}) resp = self.client.get(url) prev_rev = revision_history_log_test[per_page]['id'] next_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_rev}, query_params={'revs_breadcrumb': revision_id_test, # noqa 'per_page': per_page}) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('revision-log.html') self.assertContains(resp, '', count=per_page) self.assertContains(resp, '
  • Newer
  • ') self.assertContains(resp, '
  • Older
  • ' % escape(next_page_url)) for log in revision_history_log_test[:per_page]: author_url = reverse('browse-person', kwargs={'person_id': log['author']['id']}) revision_url = reverse('browse-revision', kwargs={'sha1_git': log['id']}) directory_url = reverse('browse-directory', kwargs={'sha1_git': log['directory']}) self.assertContains(resp, '%s' % (author_url, log['author']['name'])) self.assertContains(resp, '%s' % (revision_url, log['id'][:7])) self.assertContains(resp, '%s' % (directory_url, 'Tree')) mock_service.lookup_revision_log.return_value = \ revision_history_log_test[per_page:2*per_page+1] resp = self.client.get(next_page_url) prev_prev_rev = revision_history_log_test[2*per_page]['id'] prev_page_url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}, query_params={'per_page': per_page}) next_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_prev_rev}, query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev, # noqa 'per_page': per_page}) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('revision-log.html') self.assertContains(resp, '', count=per_page) self.assertContains(resp, '
  • Newer
  • ' % escape(prev_page_url)) self.assertContains(resp, '
  • Older
  • ' % escape(next_page_url)) mock_service.lookup_revision_log.return_value = \ revision_history_log_test[2*per_page:3*per_page+1] resp = self.client.get(next_page_url) prev_prev_prev_rev = revision_history_log_test[3*per_page]['id'] prev_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_rev}, query_params={'revs_breadcrumb': revision_id_test, # noqa 'per_page': per_page}) next_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_prev_prev_rev}, query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev + '/' + prev_prev_rev, # noqa 'per_page': per_page}) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('revision-log.html') self.assertContains(resp, '', count=per_page) self.assertContains(resp, '
  • Newer
  • ' % escape(prev_page_url)) self.assertContains(resp, '
  • Older
  • ' % escape(next_page_url)) mock_service.lookup_revision_log.return_value = \ revision_history_log_test[3*per_page:3*per_page+per_page//2] resp = self.client.get(next_page_url) prev_page_url = reverse('browse-revision-log', kwargs={'sha1_git': prev_prev_rev}, query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev, # noqa 'per_page': per_page}) self.assertEquals(resp.status_code, 200) self.assertTemplateUsed('revision-log.html') self.assertContains(resp, '', count=per_page//2) self.assertContains(resp, '
  • Older
  • ') self.assertContains(resp, '
  • Newer
  • ' % escape(prev_page_url)) @patch('swh.web.browse.views.revision.service') @istest def revision_request_errors(self, mock_service): mock_service.lookup_revision.side_effect = \ NotFoundExc('Revision not found') url = reverse('browse-revision', kwargs={'sha1_git': revision_id_test}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Revision not found', status_code=404) mock_service.lookup_revision_log.side_effect = \ NotFoundExc('Revision not found') url = reverse('browse-revision-log', kwargs={'sha1_git': revision_id_test}) resp = self.client.get(url) self.assertEquals(resp.status_code, 404) self.assertTemplateUsed('error.html') self.assertContains(resp, 'Revision not found', status_code=404) diff --git a/swh/web/tests/api/swh_api_testcase.py b/swh/web/tests/testbase.py similarity index 77% rename from swh/web/tests/api/swh_api_testcase.py rename to swh/web/tests/testbase.py index 4a59e3dc..bc312b1c 100644 --- a/swh/web/tests/api/swh_api_testcase.py +++ b/swh/web/tests/testbase.py @@ -1,70 +1,69 @@ # Copyright (C) 2015-2016 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 # Functions defined here are NOT DESIGNED FOR PRODUCTION -from rest_framework.test import APITestCase +from django.core.cache import cache from swh.storage.api.client import RemoteStorage as Storage from swh.web import config # Because the Storage's __init__ function does side effect at startup... class RemoteStorageAdapter(Storage): def __init__(self, base_url): self.base_url = base_url def _init_mock_storage(base_url='https://somewhere.org:4321'): """Instanciate a remote storage whose goal is to be mocked in a test context. NOT FOR PRODUCTION Returns: An instance of swh.storage.api.client.RemoteStorage destined to be mocked (it does not do any rest call) """ return RemoteStorageAdapter(base_url) # destined to be used as mock def create_config(base_url='https://somewhere.org:4321'): - """Function to initiate a flask app with storage designed to be mocked. + """Function to initiate swh-web config with storage designed to be mocked. Returns: - Tuple: - - app test client (for testing api, client decorator from flask) - - application's full configuration - - the storage instance to stub and mock - - the main app without any decoration + dict containing swh-web config for tests NOT FOR PRODUCTION """ storage = _init_mock_storage(base_url) swh_config = config.get_config() # inject the mock data swh_config.update({'storage': storage}) return swh_config -class SWHApiTestCase(APITestCase): +class SWHWebTestBase(object): """Testing API class. """ @classmethod def setUpClass(cls): - super(SWHApiTestCase, cls).setUpClass() + super(SWHWebTestBase, cls).setUpClass() cls.test_config = create_config() cls.maxDiff = None @classmethod def storage(cls): return cls.test_config['storage'] + + def setUp(self): + cache.clear()