diff --git a/requirements-test.txt b/requirements-test.txt index 893614ff..cbf63564 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,5 @@ pytest<4 pytest-django swh.scheduler[testing] +pytest-postgresql >= 2.1.0 + diff --git a/swh/deposit/settings/development.py b/swh/deposit/settings/development.py index 04823686..4575a26c 100644 --- a/swh/deposit/settings/development.py +++ b/swh/deposit/settings/development.py @@ -1,59 +1,59 @@ # 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 # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'development-key' # https://docs.djangoproject.com/en/1.10/ref/settings/#logging LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", # noqa 'datefmt': "%d/%b/%Y %H:%M:%S" }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'standard' }, }, 'loggers': { 'django': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }, 'django.db.backends': { 'handlers': ['console'], 'level': 'INFO', 'propagate': False, }, 'swh.deposit': { 'handlers': ['console'], 'level': 'DEBUG', }, } } # https://docs.djangoproject.com/en/1.10/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'swh-deposit-dev', + 'NAME': 'swh-deposit-dev', # this is no longer used in test env } } # https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-MEDIA_ROOT # SECURITY WARNING: Override this in the production.py module MEDIA_ROOT = '/tmp/swh-deposit/uploads/' diff --git a/swh/deposit/tests/api/test_deposit.py b/swh/deposit/tests/api/test_deposit.py index eb984002..59b1179f 100644 --- a/swh/deposit/tests/api/test_deposit.py +++ b/swh/deposit/tests/api/test_deposit.py @@ -1,160 +1,156 @@ # Copyright (C) 2017-2019 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 hashlib from django.urls import reverse from io import BytesIO from rest_framework import status from rest_framework.test import APITestCase from swh.deposit.config import COL_IRI, EDIT_SE_IRI, DEPOSIT_STATUS_REJECTED from swh.deposit.config import DEPOSIT_STATUS_PARTIAL from swh.deposit.config import DEPOSIT_STATUS_LOAD_SUCCESS from swh.deposit.config import DEPOSIT_STATUS_LOAD_FAILURE from swh.deposit.models import Deposit, DepositClient, DepositCollection from swh.deposit.parsers import parse_xml from ..common import BasicTestCase, WithAuthTestCase, CommonCreationRoutine -class DepositNoAuthCase(APITestCase, BasicTestCase): - """Deposit access are protected with basic authentication. +def test_deposit_post_will_fail_with_401(client): + """Without authentication, endpoint refuses access with 401 response """ - def test_post_will_fail_with_401(self): - """Without authentication, endpoint refuses access with 401 response + url = reverse(COL_IRI, args=['hal']) - """ - url = reverse(COL_IRI, args=[self.collection.name]) + # when + response = client.post(url) - # when - response = self.client.post(url) - - # then - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + # then + assert response.status_code == status.HTTP_401_UNAUTHORIZED class DepositFailuresTest(APITestCase, WithAuthTestCase, BasicTestCase, CommonCreationRoutine): """Deposit access are protected with basic authentication. """ def setUp(self): super().setUp() # Add another user _collection2 = DepositCollection(name='some') _collection2.save() _user = DepositClient.objects.create_user(username='user', password='user') _user.collections = [_collection2.id] self.collection2 = _collection2 def test_access_to_another_user_collection_is_forbidden(self): """Access to another user collection should return a 403 """ url = reverse(COL_IRI, args=[self.collection2.name]) response = self.client.post(url) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertRegex(response.content.decode('utf-8'), 'Client hal cannot access collection %s' % ( self.collection2.name, )) def test_delete_on_col_iri_not_supported(self): """Delete on col iri should return a 405 response """ url = reverse(COL_IRI, args=[self.collection.name]) response = self.client.delete(url) self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) self.assertRegex(response.content.decode('utf-8'), 'DELETE method is not supported on this endpoint') def create_deposit_with_rejection_status(self): url = reverse(COL_IRI, args=[self.collection.name]) data = b'some data which is clearly not a zip file' md5sum = hashlib.md5(data).hexdigest() external_id = 'some-external-id-1' # when response = self.client.post( url, content_type='application/zip', # as zip data=data, # + headers CONTENT_LENGTH=len(data), # other headers needs HTTP_ prefix to be taken into account HTTP_SLUG=external_id, HTTP_CONTENT_MD5=md5sum, HTTP_PACKAGING='http://purl.org/net/sword/package/SimpleZip', HTTP_CONTENT_DISPOSITION='attachment; filename=filename0') self.assertEqual(response.status_code, status.HTTP_201_CREATED) response_content = parse_xml(BytesIO(response.content)) actual_state = response_content['deposit_status'] self.assertEqual(actual_state, DEPOSIT_STATUS_REJECTED) def test_act_on_deposit_rejected_is_not_permitted(self): deposit_id = self.create_deposit_with_status(DEPOSIT_STATUS_REJECTED) deposit = Deposit.objects.get(pk=deposit_id) assert deposit.status == DEPOSIT_STATUS_REJECTED response = self.client.post( reverse(EDIT_SE_IRI, args=[self.collection.name, deposit_id]), content_type='application/atom+xml;type=entry', data=self.atom_entry_data1, HTTP_SLUG='external-id') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertRegex( response.content.decode('utf-8'), "You can only act on deposit with status '%s'" % ( DEPOSIT_STATUS_PARTIAL, )) def test_add_deposit_with_parent(self): # given multiple deposit already loaded deposit_id = self.create_deposit_with_status( status=DEPOSIT_STATUS_LOAD_SUCCESS, external_id='some-external-id') deposit1 = Deposit.objects.get(pk=deposit_id) self.assertIsNotNone(deposit1) self.assertEqual(deposit1.external_id, 'some-external-id') self.assertEqual(deposit1.status, DEPOSIT_STATUS_LOAD_SUCCESS) deposit_id2 = self.create_deposit_with_status( status=DEPOSIT_STATUS_LOAD_SUCCESS, external_id='some-external-id') deposit2 = Deposit.objects.get(pk=deposit_id2) self.assertIsNotNone(deposit2) self.assertEqual(deposit2.external_id, 'some-external-id') self.assertEqual(deposit2.status, DEPOSIT_STATUS_LOAD_SUCCESS) deposit_id3 = self.create_deposit_with_status( status=DEPOSIT_STATUS_LOAD_FAILURE, external_id='some-external-id') deposit3 = Deposit.objects.get(pk=deposit_id3) self.assertIsNotNone(deposit3) self.assertEqual(deposit3.external_id, 'some-external-id') self.assertEqual(deposit3.status, DEPOSIT_STATUS_LOAD_FAILURE) # when deposit_id3 = self.create_simple_deposit_partial( external_id='some-external-id') # then deposit4 = Deposit.objects.get(pk=deposit_id3) self.assertIsNotNone(deposit4) self.assertEqual(deposit4.external_id, 'some-external-id') self.assertEqual(deposit4.status, DEPOSIT_STATUS_PARTIAL) self.assertEqual(deposit4.parent, deposit2) diff --git a/swh/deposit/tests/conftest.py b/swh/deposit/tests/conftest.py new file mode 100644 index 00000000..1d65a983 --- /dev/null +++ b/swh/deposit/tests/conftest.py @@ -0,0 +1,54 @@ +# Copyright (C) 2019 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 pytest +import psycopg2 + +from django.db import connections +from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + +from swh.scheduler.tests.conftest import * # noqa + + +def execute_sql(sql): + """Execute sql to postgres db""" + with psycopg2.connect(database='postgres') as conn: + conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) + cur = conn.cursor() + cur.execute(sql) + + +@pytest.hookimpl(tryfirst=True) +def pytest_load_initial_conftests(early_config, parser, args): + """This hook is done prior to django loading. + Used to initialize the deposit's server db. + + """ + import project.app.signals + + def prepare_db(*args, **kwargs): + from django.conf import settings + db_name = 'tests' + print('before: %s' % settings.DATABASES) + # work around db settings for django + for k, v in [('ENGINE', 'django.db.backends.postgresql'), + ('NAME', 'tests'), + ('USER', postgresql_proc.user), + ('HOST', postgresql_proc.host), + ('PORT', postgresql_proc.port), + ]: + settings.DATABASES['default'][k] = v + print('after: %s' % settings.DATABASES) + execute_sql('DROP DATABASE IF EXISTS %s' % db_name) + execute_sql('CREATE DATABASE %s TEMPLATE template0' % db_name) + + project.app.signals.something = prepare_db + + + + + + + diff --git a/tox.ini b/tox.ini index f68b7019..ce035e3e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,22 +1,32 @@ [tox] envlist=flake8,py3 [testenv:py3] deps = # the dependency below is needed for now as a workaround for # https://github.com/pypa/pip/issues/6239 swh.core[http] >= 0.0.61 .[testing] pytest-cov pifpaf pytest-django commands = pifpaf run postgresql -- pytest --cov {envsitepackagesdir}/swh/deposit --cov-branch {posargs} {envsitepackagesdir}/swh/deposit + +[testenv:py3-dev] +deps = + .[testing] + pytest-cov + ipdb +commands = + pytest {envsitepackagesdir}/swh/deposit/ {posargs} + + [testenv:flake8] skip_install = true deps = flake8 commands = {envpython} -m flake8 \ --exclude=.tox,.git,__pycache__,.tox,.eggs,*.egg,swh/deposit/migrations