diff --git a/swh/deposit/api/deposit_update.py b/swh/deposit/api/deposit_update.py
index 4a0d5975..42af22e0 100644
--- a/swh/deposit/api/deposit_update.py
+++ b/swh/deposit/api/deposit_update.py
@@ -1,155 +1,155 @@
# Copyright (C) 2017-2018 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 rest_framework import status
from .common import SWHPostDepositAPI, SWHPutDepositAPI, SWHDeleteDepositAPI
from .common import ACCEPT_ARCHIVE_CONTENT_TYPES
from ..config import CONT_FILE_IRI, EDIT_SE_IRI, EM_IRI
-from ..errors import make_error_response, BAD_REQUEST
+from ..errors import make_error_dict, BAD_REQUEST
from ..parsers import SWHFileUploadZipParser, SWHFileUploadTarParser
from ..parsers import SWHAtomEntryParser
from ..parsers import SWHMultiPartParser
class SWHUpdateArchiveDeposit(SWHPostDepositAPI, SWHPutDepositAPI,
SWHDeleteDepositAPI):
"""Deposit request class defining api endpoints for sword deposit.
What's known as 'EM IRI' in the sword specification.
HTTP verbs supported: PUT, POST, DELETE
"""
parser_classes = (SWHFileUploadZipParser, SWHFileUploadTarParser, )
def process_put(self, req, headers, collection_name, deposit_id):
"""Replace existing content for the existing deposit.
source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_editingcontent_binary
Returns:
204 No content
"""
if req.content_type not in ACCEPT_ARCHIVE_CONTENT_TYPES:
msg = 'Packaging format supported is restricted to %s' % (
', '.join(ACCEPT_ARCHIVE_CONTENT_TYPES))
- return make_error_response(req, BAD_REQUEST, msg)
+ return make_error_dict(BAD_REQUEST, msg)
return self._binary_upload(req, headers, collection_name,
deposit_id=deposit_id,
replace_archives=True)
def process_post(self, req, headers, collection_name, deposit_id):
"""Add new content to the existing deposit.
source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_addingcontent_mediaresource
Returns:
201 Created
Headers: Location: [Cont-File-IRI]
Body: [optional Deposit Receipt]
"""
if req.content_type not in ACCEPT_ARCHIVE_CONTENT_TYPES:
msg = 'Packaging format supported is restricted to %s' % (
', '.join(ACCEPT_ARCHIVE_CONTENT_TYPES))
- return make_error_response(req, BAD_REQUEST, msg)
+ return 'unused', 'unused', make_error_dict(BAD_REQUEST, msg)
return (status.HTTP_201_CREATED, CONT_FILE_IRI,
self._binary_upload(req, headers, collection_name, deposit_id))
def process_delete(self, req, collection_name, deposit_id):
"""Delete content (archives) from existing deposit.
source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_deletingcontent
Returns:
204 Created
"""
return self._delete_archives(collection_name, deposit_id)
class SWHUpdateMetadataDeposit(SWHPostDepositAPI, SWHPutDepositAPI,
SWHDeleteDepositAPI):
"""Deposit request class defining api endpoints for sword deposit.
What's known as 'Edit IRI' (and SE IRI) in the sword specification.
HTTP verbs supported: POST (SE IRI), PUT (Edit IRI), DELETE
"""
parser_classes = (SWHMultiPartParser, SWHAtomEntryParser)
def process_put(self, req, headers, collection_name, deposit_id):
"""Replace existing deposit's metadata/archive with new ones.
source:
- http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_editingcontent_metadata
- http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_editingcontent_multipart
Returns:
204 No content
"""
if req.content_type.startswith('multipart/'):
return self._multipart_upload(req, headers, collection_name,
deposit_id=deposit_id,
replace_archives=True,
replace_metadata=True)
return self._atom_entry(req, headers, collection_name,
deposit_id=deposit_id, replace_metadata=True)
def process_post(self, req, headers, collection_name, deposit_id):
"""Add new metadata/archive to existing deposit.
source:
- http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_addingcontent_metadata
- http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_addingcontent_multipart
This also deals with an empty post corner case to finalize a
deposit.
Returns:
In optimal case for a multipart and atom-entry update, a
201 Created response. The body response will hold a
deposit. And the response headers will contain an entry
'Location' with the EM-IRI.
For the empty post case, this returns a 200.
"""
if req.content_type.startswith('multipart/'):
return (status.HTTP_201_CREATED, EM_IRI,
self._multipart_upload(req, headers, collection_name,
deposit_id=deposit_id))
# check for final empty post
# source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
# #continueddeposit_complete
if headers['content-length'] == 0 and headers['in-progress'] is False:
data = self._empty_post(req, headers, collection_name, deposit_id)
return (status.HTTP_200_OK, EDIT_SE_IRI, data)
return (status.HTTP_201_CREATED, EM_IRI,
self._atom_entry(req, headers, collection_name,
deposit_id=deposit_id))
def process_delete(self, req, collection_name, deposit_id):
"""Delete the container (deposit).
Source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html
#protocoloperations_deleteconteiner
"""
return self._delete_deposit(collection_name, deposit_id)
diff --git a/swh/deposit/tests/api/test_deposit_update.py b/swh/deposit/tests/api/test_deposit_update.py
index de40e70f..cf911e7b 100644
--- a/swh/deposit/tests/api/test_deposit_update.py
+++ b/swh/deposit/tests/api/test_deposit_update.py
@@ -1,337 +1,342 @@
# 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 django.core.urlresolvers import reverse
from nose.tools import istest
from rest_framework import status
from rest_framework.test import APITestCase
from swh.deposit.models import Deposit, DepositRequest
from swh.deposit.config import EDIT_SE_IRI, EM_IRI
from ..common import BasicTestCase, WithAuthTestCase, CommonCreationRoutine
from ..common import FileSystemCreationRoutine, create_arborescence_zip
class DepositUpdateOrReplaceExistingDataTest(
APITestCase, WithAuthTestCase, BasicTestCase,
FileSystemCreationRoutine, CommonCreationRoutine):
"""Try put/post (update/replace) query on EM_IRI
"""
def setUp(self):
super().setUp()
self.atom_entry_data1 = b"""
bar
"""
self.atom_entry_data1 = b"""
bar
"""
self.archive2 = create_arborescence_zip(
self.root_path, 'archive2', 'file2', b'some other content in file')
@istest
def replace_archive_to_deposit_is_possible(self):
"""Replace all archive with another one should return a 204 response
"""
# given
deposit_id = self.create_simple_binary_deposit(status_partial=True)
deposit = Deposit.objects.get(pk=deposit_id)
requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['archive'])
assert len(list(requests)) == 1
assert self.archive['name'] in requests[0].archive.name
# we have no metadata for that deposit
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['metadata']))
assert len(requests) == 0
deposit_id = self._update_deposit_with_status(deposit_id,
status_partial=True)
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['metadata']))
assert len(requests) == 1
update_uri = reverse(EM_IRI, args=[self.collection.name, deposit_id])
external_id = 'some-external-id-1'
response = self.client.put(
update_uri,
content_type='application/zip', # as zip
data=self.archive2['data'],
# + headers
CONTENT_LENGTH=self.archive2['length'],
HTTP_SLUG=external_id,
HTTP_CONTENT_MD5=self.archive2['md5sum'],
HTTP_PACKAGING='http://purl.org/net/sword/package/SimpleZip',
HTTP_IN_PROGRESS='false',
HTTP_CONTENT_DISPOSITION='attachment; filename=%s' % (
self.archive2['name'], ))
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['archive'])
self.assertEquals(len(list(requests)), 1)
self.assertRegex(requests[0].archive.name, self.archive2['name'])
# check we did not touch the other parts
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['metadata']))
self.assertEquals(len(requests), 1)
@istest
def replace_metadata_to_deposit_is_possible(self):
"""Replace all metadata with another one should return a 204 response
"""
# given
deposit_id = self.create_simple_binary_deposit(status_partial=True)
deposit = Deposit.objects.get(pk=deposit_id)
requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['metadata'])
assert len(list(requests)) == 0
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['archive']))
assert len(requests) == 1
update_uri = reverse(EDIT_SE_IRI, args=[self.collection.name,
deposit_id])
response = self.client.put(
update_uri,
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data1)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['metadata'])
self.assertEquals(len(list(requests)), 1)
metadata = requests[0].metadata
self.assertEquals(metadata["{http://www.w3.org/2005/Atom}foobar"],
'bar')
# check we did not touch the other parts
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['archive']))
self.assertEquals(len(requests), 1)
@istest
def add_archive_to_deposit_is_possible(self):
"""Add another archive to a deposit return a 201 response
"""
# given
deposit_id = self.create_simple_binary_deposit(status_partial=True)
deposit = Deposit.objects.get(pk=deposit_id)
requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['archive'])
assert len(list(requests)) == 1
assert self.archive['name'] in requests[0].archive.name
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['metadata']))
assert len(requests) == 0
update_uri = reverse(EM_IRI, args=[self.collection.name, deposit_id])
external_id = 'some-external-id-1'
response = self.client.post(
update_uri,
content_type='application/zip', # as zip
data=self.archive2['data'],
# + headers
CONTENT_LENGTH=self.archive2['length'],
HTTP_SLUG=external_id,
HTTP_CONTENT_MD5=self.archive2['md5sum'],
HTTP_PACKAGING='http://purl.org/net/sword/package/SimpleZip',
HTTP_IN_PROGRESS='false',
HTTP_CONTENT_DISPOSITION='attachment; filename=%s' % (
self.archive2['name'],))
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
requests = list(DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['archive']).order_by('id'))
self.assertEquals(len(requests), 2)
# first archive still exists
self.assertRegex(requests[0].archive.name, self.archive['name'])
# a new one was added
self.assertRegex(requests[1].archive.name, self.archive2['name'])
# check we did not touch the other parts
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['metadata']))
self.assertEquals(len(requests), 0)
@istest
def add_metadata_to_deposit_is_possible(self):
"""Add metadata with another one should return a 204 response
"""
# given
deposit_id = self.create_deposit_partial()
deposit = Deposit.objects.get(pk=deposit_id)
requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['metadata'])
assert len(list(requests)) == 2
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['archive']))
assert len(requests) == 0
update_uri = reverse(EDIT_SE_IRI, args=[self.collection.name,
deposit_id])
response = self.client.post(
update_uri,
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data1)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
requests = DepositRequest.objects.filter(
deposit=deposit,
type=self.deposit_request_types['metadata']).order_by('id')
self.assertEquals(len(list(requests)), 3)
# a new one was added
self.assertEquals(requests[1].metadata[
"{http://www.w3.org/2005/Atom}foobar"], 'bar')
# check we did not touch the other parts
requests = list(DepositRequest.objects.filter(
deposit=deposit, type=self.deposit_request_types['archive']))
self.assertEquals(len(requests), 0)
class DepositUpdateFailuresTest(APITestCase, WithAuthTestCase, BasicTestCase,
CommonCreationRoutine):
"""Failure scenario about add/replace (post/put) query on deposit.
"""
@istest
def add_metadata_to_unknown_collection(self):
"""Replacing metadata to unknown deposit should return a 404 response
"""
url = reverse(EDIT_SE_IRI,
args=['unknown', 999]),
response = self.client.post(
url,
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data0)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@istest
def add_metadata_to_unknown_deposit(self):
"""Replacing metadata to unknown deposit should return a 404 response
"""
url = reverse(EDIT_SE_IRI,
args=[self.collection.name, 999]),
response = self.client.post(
url,
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data0)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@istest
def replace_metadata_to_unknown_deposit(self):
"""Adding metadata to unknown deposit should return a 404 response
"""
url = reverse(EDIT_SE_IRI,
args=[self.collection.name, 999]),
response = self.client.put(
url,
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data0)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@istest
def add_archive_to_unknown_deposit(self):
"""Adding metadata to unknown deposit should return a 404 response
"""
url = reverse(EM_IRI,
args=[self.collection.name, 999]),
response = self.client.post(
url,
content_type='application/zip',
data=self.atom_entry_data0)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@istest
def replace_archive_to_unknown_deposit(self):
"""Replacing archive to unknown deposit should return a 404 response
"""
url = reverse(EM_IRI,
args=[self.collection.name, 999]),
response = self.client.put(
url,
content_type='application/zip',
data=self.atom_entry_data0)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@istest
def post_metadata_to_em_iri_failure(self):
- """Add archive with wrong content type should return a 400 response
+ """Update (POST) archive with wrong content type should return 400
"""
- deposit_id = self.create_deposit_ready()
-
+ deposit_id = self.create_deposit_partial() # only update on partial
update_uri = reverse(EM_IRI, args=[self.collection.name, deposit_id])
- response = self.client.put(
+ response = self.client.post(
update_uri,
- content_type='application/binary',
+ content_type='application/x-gtar-compressed',
data=self.atom_entry_data0)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertRegex(response.content.decode('utf-8'),
+ 'Packaging format supported is restricted to '
+ 'application/zip, application/x-tar')
@istest
def put_metadata_to_em_iri_failure(self):
- """Update archive with wrong content type should return 400 response
+ """Update (PUT) archive with wrong content type should return 400
"""
# given
- deposit_id = self.create_deposit_ready()
+ deposit_id = self.create_deposit_partial() # only update on partial
# when
update_uri = reverse(EM_IRI, args=[self.collection.name, deposit_id])
response = self.client.put(
update_uri,
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data0)
# then
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertRegex(response.content.decode('utf-8'),
+ 'Packaging format supported is restricted to '
+ 'application/zip, application/x-tar')