diff --git a/swh/deposit/tests/api/test_deposit.py b/swh/deposit/tests/api/test_deposit.py
index 59b1179f..d5dfe69f 100644
--- a/swh/deposit/tests/api/test_deposit.py
+++ b/swh/deposit/tests/api/test_deposit.py
@@ -1,156 +1,189 @@
 # 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 swh.deposit.config import (
+    COL_IRI, EDIT_SE_IRI, DEPOSIT_STATUS_REJECTED,
+    DEPOSIT_STATUS_PARTIAL, DEPOSIT_STATUS_LOAD_SUCCESS,
+    DEPOSIT_STATUS_LOAD_FAILURE
+)
 
-from ..common import BasicTestCase, WithAuthTestCase, CommonCreationRoutine
+from swh.deposit.models import Deposit
+from swh.deposit.parsers import parse_xml
 
 
 def test_deposit_post_will_fail_with_401(client):
     """Without authentication, endpoint refuses access with 401 response
 
     """
     url = reverse(COL_IRI, args=['hal'])
+    response = client.post(url)
+    assert response.status_code == status.HTTP_401_UNAUTHORIZED
+
+
+def test_access_to_another_user_collection_is_forbidden(
+        authenticated_client, deposit_another_collection, deposit_user):
+    """Access to another user collection should return a 403
+
+    """
+    coll2 = deposit_another_collection
+    url = reverse(COL_IRI, args=[coll2.name])
+    response = authenticated_client.post(url)
+    assert response.status_code == status.HTTP_403_FORBIDDEN
+    msg = 'Client %s cannot access collection %s' % (
+        deposit_user.username, coll2.name, )
+    assert msg in response.content.decode('utf-8')
+
+
+def test_delete_on_col_iri_not_supported(
+        authenticated_client, deposit_collection):
+    """Delete on col iri should return a 405 response
+
+    """
+    url = reverse(COL_IRI, args=[deposit_collection.name])
+    response = authenticated_client.delete(url)
+    assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
+    assert 'DELETE method is not supported on this endpoint' in \
+        response.content.decode('utf-8')
+
+
+def create_deposit_with_rejection_status(
+        authenticated_client, deposit_collection):
+    url = reverse(COL_IRI, args=[deposit_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 = client.post(url)
+    response = authenticated_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')
+
+    assert response.status_code == status.HTTP_201_CREATED
+    response_content = parse_xml(BytesIO(response.content))
+    actual_state = response_content['deposit_status']
+    assert actual_state == DEPOSIT_STATUS_REJECTED
+
+
+def test_act_on_deposit_rejected_is_not_permitted(
+        authenticated_client, deposit_collection, rejected_deposit,
+        atom_dataset):
+    deposit = rejected_deposit
+
+    response = authenticated_client.post(
+        reverse(EDIT_SE_IRI, args=[deposit.collection.name, deposit.id]),
+        content_type='application/atom+xml;type=entry',
+        data=atom_dataset['entry-data1'],
+        HTTP_SLUG=deposit.external_id)
+
+    assert response.status_code == status.HTTP_400_BAD_REQUEST
+    msg = 'You can only act on deposit with status '%s'' % (
+        DEPOSIT_STATUS_PARTIAL, )
+    assert msg in response.content.decode('utf-8')
+
+
+def test_add_deposit_when_partial_makes_new_deposit(
+        authenticated_client, deposit_collection, partial_deposit,
+        atom_dataset):
+    """Posting deposit on collection when previous is partial makes new deposit
 
-    # then
-    assert response.status_code == status.HTTP_401_UNAUTHORIZED
+    """
+    deposit = partial_deposit
+    assert deposit.status == DEPOSIT_STATUS_PARTIAL
+
+    # adding a new deposit with the same external id
+    response = authenticated_client.post(
+        reverse(COL_IRI, args=[deposit_collection.name]),
+        content_type='application/atom+xml;type=entry',
+        data=atom_dataset['entry-data0'] % deposit.external_id.encode('utf-8'),
+        HTTP_SLUG=deposit.external_id
+    )
+
+    assert response.status_code == status.HTTP_201_CREATED
+    response_content = parse_xml(BytesIO(response.content))
+    deposit_id = response_content['deposit_id']
+
+    assert deposit_id != deposit.id  # new deposit
+
+    new_deposit = Deposit.objects.get(pk=deposit_id)
+    assert new_deposit != deposit
+    assert new_deposit.parent is None
+
+
+def test_add_deposit_when_failed_makes_new_deposit_with_no_parent(
+        authenticated_client, deposit_collection, failed_deposit,
+        atom_dataset):
+    """Posting deposit on collection when deposit done makes new deposit with
+    parent
+
+    """
+    deposit = failed_deposit
+    assert deposit.status == DEPOSIT_STATUS_LOAD_FAILURE
+
+    # adding a new deposit with the same external id as a completed deposit
+    # creates the parenting chain
+    response = authenticated_client.post(
+        reverse(COL_IRI, args=[deposit_collection.name]),
+        content_type='application/atom+xml;type=entry',
+        data=atom_dataset['entry-data0'] % deposit.external_id.encode('utf-8'),
+        HTTP_SLUG=deposit.external_id)
+
+    assert response.status_code == status.HTTP_201_CREATED
+    response_content = parse_xml(BytesIO(response.content))
+    deposit_id = response_content['deposit_id']
+
+    assert deposit_id != deposit.id
+
+    new_deposit = Deposit.objects.get(pk=deposit_id)
+    assert new_deposit != deposit
+    assert new_deposit.parent is None
 
 
-class DepositFailuresTest(APITestCase, WithAuthTestCase, BasicTestCase,
-                          CommonCreationRoutine):
-    """Deposit access are protected with basic authentication.
+def test_add_deposit_when_done_makes_new_deposit_with_parent_old_one(
+        authenticated_client, deposit_collection, completed_deposit,
+        atom_dataset):
+    """Posting deposit on collection when deposit done makes new deposit with
+    parent
 
     """
-    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)
+    # given multiple deposit already loaded
+    deposit = completed_deposit
+    assert deposit.status == DEPOSIT_STATUS_LOAD_SUCCESS
+
+    # adding a new deposit with the same external id as a completed deposit
+    # creates the parenting chain
+    response = authenticated_client.post(
+        reverse(COL_IRI, args=[deposit_collection.name]),
+        content_type='application/atom+xml;type=entry',
+        data=atom_dataset['entry-data0'] % deposit.external_id.encode('utf-8'),
+        HTTP_SLUG=deposit.external_id
+    )
+
+    assert response.status_code == status.HTTP_201_CREATED
+    response_content = parse_xml(BytesIO(response.content))
+    deposit_id = response_content['deposit_id']
+
+    assert deposit_id != deposit.id
+
+    new_deposit = Deposit.objects.get(pk=deposit_id)
+    assert deposit.collection == new_deposit.collection
+    assert deposit.external_id == new_deposit.external_id
+
+    assert new_deposit != deposit
+    assert new_deposit.parent == deposit
diff --git a/swh/deposit/tests/conftest.py b/swh/deposit/tests/conftest.py
index bd76ea38..b816e5f5 100644
--- a/swh/deposit/tests/conftest.py
+++ b/swh/deposit/tests/conftest.py
@@ -1,259 +1,257 @@
 # 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 base64
 import pytest
 import psycopg2
 
 from django.urls import reverse
 from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
 from rest_framework import status
 from rest_framework.test import APIClient
 
 # , STATE_IRI,
 from swh.scheduler.tests.conftest import *  # noqa
 
 from swh.deposit.config import (
     COL_IRI, EDIT_SE_IRI, DEPOSIT_STATUS_DEPOSITED, DEPOSIT_STATUS_REJECTED,
-    DEPOSIT_STATUS_PARTIAL, DEPOSIT_STATUS_LOAD_SUCCESS
+    DEPOSIT_STATUS_PARTIAL, DEPOSIT_STATUS_LOAD_SUCCESS,
+    DEPOSIT_STATUS_LOAD_FAILURE
 )
 from swh.deposit.tests.common import create_arborescence_archive
 
 
 TEST_USER = {
     'username': 'test',
     'password': 'password',
     'email': 'test@example.org',
     'provider_url': 'https://hal-test.archives-ouvertes.fr/',
     'domain': 'archives-ouvertes.fr/',
     'collection': {
         'name': 'test'
     },
 }
 
 
 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),  # noqa
                 ('HOST', postgresql_proc.host),  # noqa
                 ('PORT', postgresql_proc.port),  # noqa
         ]:
             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
 
 
-@pytest.fixture
-def deposit_collection(db):
+def create_deposit_collection(collection_name: str):
+    """Create a deposit collection with name collection_name
+
+    """
     from swh.deposit.models import DepositCollection
-    collection_name = TEST_USER['collection']['name']
     try:
         collection = DepositCollection._default_manager.get(
             name=collection_name)
     except DepositCollection.DoesNotExist:
         collection = DepositCollection(name=collection_name)
         collection.save()
     return collection
 
 
+def deposit_collection_factory(collection_name=TEST_USER['collection']['name']):
+    @pytest.fixture
+    def _deposit_collection(db, collection_name=collection_name):
+        return create_deposit_collection(collection_name)
+
+    return _deposit_collection
+
+
+deposit_collection = deposit_collection_factory()
+deposit_another_collection = deposit_collection_factory('another-collection')
+
+
 @pytest.fixture
 def deposit_user(db, deposit_collection):
     """Create/Return the test_user "test"
 
     """
     from swh.deposit.models import DepositClient
     try:
         user = DepositClient._default_manager.get(
             username=TEST_USER['username'])
     except DepositClient.DoesNotExist:
         user = DepositClient._default_manager.create_user(
             username=TEST_USER['username'],
             email=TEST_USER['email'],
             password=TEST_USER['password'],
             provider_url=TEST_USER['provider_url'],
             domain=TEST_USER['domain'],
         )
         user.collections = [deposit_collection.id]
         user.save()
     return user
 
 
 @pytest.fixture
 def client():
     """Override pytest-django one which does not work for djangorestframework.
 
     """
     return APIClient()  # <- drf's client
 
 
 @pytest.yield_fixture
 def authenticated_client(client, deposit_user):
     """Returned a logged client
 
     """
     _token = '%s:%s' % (deposit_user.username, TEST_USER['password'])
     token = base64.b64encode(_token.encode('utf-8'))
     authorization = 'Basic %s' % token.decode('utf-8')
     client.credentials(HTTP_AUTHORIZATION=authorization)
     yield client
     client.logout()
 
 
 @pytest.fixture
 def sample_archive(tmp_path):
     """Returns a sample archive
 
     """
     tmp_path = str(tmp_path)  # pytest version limitation in previous version
     archive = create_arborescence_archive(
         tmp_path, 'archive1', 'file1', b'some content in file')
 
     return archive
 
 
 def create_deposit(
         authenticated_client, collection_name: str, sample_archive,
-        external_id: str):
+        external_id: str, deposit_status=DEPOSIT_STATUS_DEPOSITED):
     """Create a skeleton shell deposit
 
     """
     url = reverse(COL_IRI, args=[collection_name])
     # when
     response = authenticated_client.post(
         url,
         content_type='application/zip',  # as zip
         data=sample_archive['data'],
         # + headers
         CONTENT_LENGTH=sample_archive['length'],
         HTTP_SLUG=external_id,
         HTTP_CONTENT_MD5=sample_archive['md5sum'],
         HTTP_PACKAGING='http://purl.org/net/sword/package/SimpleZip',
         HTTP_IN_PROGRESS='false',
         HTTP_CONTENT_DISPOSITION='attachment; filename=filename0')
 
     # then
     assert response.status_code == status.HTTP_201_CREATED
     from swh.deposit.models import Deposit
     deposit = Deposit._default_manager.get(external_id=external_id)
-    return deposit
-
-
-@pytest.fixture
-def deposited_deposit(
-        sample_archive, deposit_collection, authenticated_client):
-    """Returns a deposit with status 'deposited'.
 
-    """
-    deposit = create_deposit(
-        authenticated_client, deposit_collection.name, sample_archive,
-        external_id='external-id-deposited')
-    assert deposit.status == DEPOSIT_STATUS_DEPOSITED
+    if deposit.status != deposit_status:
+        deposit.status = deposit_status
+        deposit.save()
+    assert deposit.status == deposit_status
     return deposit
 
 
-@pytest.fixture
-def rejected_deposit(sample_archive, deposit_collection, authenticated_client):
-    """Returns a deposit with status 'rejected'.
+def deposit_factory(deposit_status=DEPOSIT_STATUS_DEPOSITED):
+    """Build deposit with a specific status
 
     """
-    deposit = create_deposit(
-        authenticated_client, deposit_collection.name, sample_archive,
-        external_id='external-id-rejected')
-    deposit.status = DEPOSIT_STATUS_REJECTED
-    deposit.save()
-    assert deposit.status == DEPOSIT_STATUS_REJECTED
-    return deposit
+    @pytest.fixture()
+    def _deposit(sample_archive, deposit_collection, authenticated_client,
+                 deposit_status=deposit_status):
+        external_id = 'external-id-%s' % deposit_status
+        return create_deposit(
+            authenticated_client, deposit_collection.name, sample_archive,
+            external_id=external_id, deposit_status=deposit_status
+        )
 
+    return _deposit
 
-@pytest.fixture
-def partial_deposit(sample_archive, deposit_collection, authenticated_client):
-    """Returns a deposit with status 'partial'.
 
-    """
-    deposit = create_deposit(
-        authenticated_client, deposit_collection.name, sample_archive,
-        external_id='external-id-partial'
-    )
-    deposit.status = DEPOSIT_STATUS_PARTIAL
-    deposit.save()
-    assert deposit.status == DEPOSIT_STATUS_PARTIAL
-    return deposit
+deposited_deposit = deposit_factory()
+rejected_deposit = deposit_factory(deposit_status=DEPOSIT_STATUS_REJECTED)
+partial_deposit = deposit_factory(deposit_status=DEPOSIT_STATUS_PARTIAL)
+completed_deposit = deposit_factory(deposit_status=DEPOSIT_STATUS_LOAD_SUCCESS)
+failed_deposit = deposit_factory(deposit_status=DEPOSIT_STATUS_LOAD_FAILURE)
 
 
 @pytest.fixture
 def partial_deposit_with_metadata(
         sample_archive, deposit_collection, authenticated_client,
         atom_dataset):
     """Returns deposit with archive and metadata provided, status 'partial'
 
     """
     # deposit with one archive
     deposit = create_deposit(
         authenticated_client, deposit_collection.name, sample_archive,
-        external_id='external-id-partial'
+        external_id='external-id-partial',
+        deposit_status=DEPOSIT_STATUS_PARTIAL
     )
-    deposit.status = DEPOSIT_STATUS_PARTIAL
-    deposit.save()
-    assert deposit.status == DEPOSIT_STATUS_PARTIAL
 
     # update the deposit with metadata
     response = authenticated_client.post(
         reverse(EDIT_SE_IRI, args=[deposit_collection.name, deposit.id]),
         content_type='application/atom+xml;type=entry',
         data=atom_dataset['entry-data0'] % deposit.external_id.encode('utf-8'),
         HTTP_SLUG=deposit.external_id,
         HTTP_IN_PROGRESS='true')
 
     assert response.status_code == status.HTTP_201_CREATED
     assert deposit.status == DEPOSIT_STATUS_PARTIAL
     return deposit
 
 
 @pytest.fixture
 def complete_deposit(sample_archive, deposit_collection, authenticated_client):
     """Returns a completed deposit (load success)
 
     """
     deposit = create_deposit(
         authenticated_client, deposit_collection.name, sample_archive,
-        external_id='external-id-complete'
+        external_id='external-id-complete',
+        deposit_status=DEPOSIT_STATUS_LOAD_SUCCESS
     )
-    deposit.status = DEPOSIT_STATUS_LOAD_SUCCESS
     _swh_id_context = 'https://hal.archives-ouvertes.fr/hal-01727745'
     deposit.swh_id = 'swh:1:dir:42a13fc721c8716ff695d0d62fc851d641f3a12b'
     deposit.swh_id_context = '%s;%s' % (
         deposit.swh_id, _swh_id_context)
     deposit.swh_anchor_id = \
         'swh:rev:1:548b3c0a2bb43e1fca191e24b5803ff6b3bc7c10'
     deposit.swh_anchor_id_context = '%s;%s' % (
         deposit.swh_anchor_id, _swh_id_context)
     deposit.save()
     return deposit