diff --git a/swh/deposit/parsers.py b/swh/deposit/parsers.py
index aa19338d..8dfbc761 100644
--- a/swh/deposit/parsers.py
+++ b/swh/deposit/parsers.py
@@ -1,47 +1,62 @@
# 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
"""Module in charge of defining parsers with SWORD 2.0 supported mediatypes.
"""
+from decimal import Decimal
from rest_framework.parsers import FileUploadParser
from rest_framework.parsers import MultiPartParser
from rest_framework_xml.parsers import XMLParser
class SWHFileUploadParser(FileUploadParser):
"""File upload parser limited to zip archive.
"""
media_type = 'application/zip'
-class SWHAtomEntryParser(XMLParser):
+class SWHXMLParser(XMLParser):
+ def _type_convert(self, value):
+ """Override the default type converter to avoid having decimal in the
+ resulting output.
+
+ """
+ value = super()._type_convert(value)
+ if isinstance(value, Decimal):
+ print(value)
+ value = str(value)
+
+ return value
+
+
+class SWHAtomEntryParser(SWHXMLParser):
"""Atom entry parser limited to specific mediatype
"""
media_type = 'application/atom+xml;type=entry'
class SWHMultiPartParser(MultiPartParser):
"""Multipart parser limited to a subset of mediatypes.
"""
media_type = 'multipart/*; *'
def parse_xml(raw_content):
"""Parse xml body.
Args:
raw_content (bytes): The content to parse
Returns:
content parsed as dict.
"""
- return XMLParser().parse(raw_content)
+ return SWHXMLParser().parse(raw_content)
diff --git a/swh/deposit/tests/api/test_deposit_atom.py b/swh/deposit/tests/api/test_deposit_atom.py
index 761bac0b..399047a3 100644
--- a/swh/deposit/tests/api/test_deposit_atom.py
+++ b/swh/deposit/tests/api/test_deposit_atom.py
@@ -1,455 +1,524 @@
# 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 io import BytesIO
from nose.tools import istest
from rest_framework import status
from rest_framework.test import APITestCase
from swh.deposit.config import COL_IRI, DEPOSIT_STATUS_READY
from swh.deposit.models import Deposit, DepositRequest
from swh.deposit.parsers import parse_xml
from ..common import BasicTestCase, WithAuthTestCase
class DepositAtomEntryTestCase(APITestCase, WithAuthTestCase, BasicTestCase):
"""Try and post atom entry deposit.
"""
def setUp(self):
super().setUp()
self.atom_entry_data0 = b"""
Awesome Compiler
hal
urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a
%s
2017-10-07T15:17:08Z
some awesome author
something
awesome-compiler
This is an awesome compiler destined to
awesomely compile stuff
and other stuff
compiler,programming,language
2005-10-07T17:17:08Z
2005-10-07T17:17:08Z
release note
related link
Awesome
https://hoster.org/awesome-compiler
GNU/Linux
0.0.1
running
all
"""
self.atom_entry_data1 = b"""
hal
urn:uuid:2225c695-cfb8-4ebb-aaaa-80da344efa6a
2017-10-07T15:17:08Z
some awesome author
something
awesome-compiler
This is an awesome compiler destined to
awesomely compile stuff
and other stuff
compiler,programming,language
2005-10-07T17:17:08Z
2005-10-07T17:17:08Z
release note
related link
Awesome
https://hoster.org/awesome-compiler
GNU/Linux
0.0.1
running
all
"""
self.atom_entry_data2 = b"""
%s
"""
self.atom_entry_data_empty_body = b"""
"""
self.atom_entry_data3 = b"""
something
"""
self.atom_entry_data_atom_only = b"""
Awesome Compiler
urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a
1785io25c695
2017-10-07T15:17:08Z
some awesome author
"""
self.atom_entry_data_codemeta = b"""
Awesome Compiler
urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a
1785io25c695
1785io25c695
origin url
other identifier, DOI, ARK
Domain
description
key-word 1
key-word 2
creation date
publication date
comment
article name
article id
Collaboration/Projet
project name
id
see also
Sponsor A
Sponsor B
Platform/OS
dependencies
Version
active
license
url spdx
.Net Framework 3.0
Python2.3
author1
Inria
UPMC
author2
Inria
UPMC
http://code.com
language 1
language 2
http://issuetracker.com
""" # noqa
self.atom_entry_data_dc_codemeta = b"""
%s
hal-01587361
https://hal.inria.fr/hal-01587361
https://hal.inria.fr/hal-01587361/document
https://hal.inria.fr/hal-01587361/file/AffectationRO-v1.0.0.zip
doi:10.5281/zenodo.438684
The assignment problem
AffectationRO
Gruenpeter, Morane
[INFO] Computer Science [cs]
[INFO.INFO-RO] Computer Science [cs]/Operations Research [cs.RO]
SOFTWARE
Project in OR: The assignment problemA java implementation for the assignment problem first release
description fr
2015-06-01
2017-10-19
en
url stable
Version sur hal
Version entre par lutilisateur
Mots-cls
Commentaire
Rfrence interne
Collaboration/Projet
nom du projet
id
Voir aussi
Financement
Projet ANR
Projet Europen
Platform/OS
Dpendances
Etat du dveloppement
license
url spdx
Outils de dveloppement- outil no1
Outils de dveloppement- outil no2
http://code.com
language 1
language 2
""" # noqa
self.atom_entry_tei = b"""HAL TEI export of hal-01587083CCSDDistributed under a Creative Commons Attribution 4.0 International LicenseHAL API platform
questionnaire software metadataMoraneGruenpeter7de56c632362954fa84172cad80afe4einria.fr1556733MoraneGruenpeterf85a43a5fb4a2e0778a77e017f28c8fdgmail.com2017-09-29 11:21:322017-10-03 17:20:132017-10-03 17:20:132017-09-292017-09-29contributorMoraneGruenpeterf85a43a5fb4a2e0778a77e017f28c8fdgmail.comCCSDhal-01587083https://hal.inria.fr/hal-01587083gruenpeter:hal-0158708320172017questionnaire software metadataMoraneGruenpeter7de56c632362954fa84172cad80afe4einria.fr1556733EnglishComputer Science [cs]SoftwareIRILLInitiative pour la Recherche et l'Innovation sur le Logiciel Libre[https://www.irill.org/]Universite Pierre et Marie Curie - Paris 6UPMC4 place Jussieu - 75005 Paris[http://www.upmc.fr/]Institut National de Recherche en Informatique et en AutomatiqueInriaDomaine de VoluceauRocquencourt - BP 10578153 Le Chesnay Cedex[http://www.inria.fr/en/]Universite Paris Diderot - Paris 7UPD75 rue Thomas-Mann - 75205 Paris cedex 13[http://www.univ-paris-diderot.fr]""" # noqa
self.atom_entry_data_badly_formatted = b"""
"""
+ self.atom_error_with_decimal = b"""
+
+ Composing a Web of Audio Applications
+ hal
+ hal-01243065
+ hal-01243065
+ https://hal-test.archives-ouvertes.fr/hal-01243065
+ test
+
+
+ DSP programming,Web,Composability,Faust
+ 2017-05-03T16:08:47+02:00
+ The Web offers a great opportunity to share, deploy and use programs without installation difficulties. In this article we explore the idea of freely combining/composing real-time audio applications deployed on the Web using Faust audio DSP language.
+ 1
+ 10.4
+ phpstorm
+ stable
+
+ linux
+ php
+ python
+ C
+
+ GNU General Public License v3.0 only
+
+
+ CeCILL Free Software License Agreement v1.1
+
+
+ HAL
+ hal@ccsd.cnrs.fr
+
+
+ Someone Nice
+ someone@nice.fr
+ FFJ
+
+
+""" # noqa
+
+ @istest
+ def post_deposit_atom_entry_serialization_error(self):
+ """Posting an initial atom entry should return 201 with deposit receipt
+
+ """
+ # given
+ # when
+ response = self.client.post(
+ reverse(COL_IRI, args=[self.collection.name]),
+ content_type='application/atom+xml;type=entry',
+ data=self.atom_error_with_decimal,
+ HTTP_SLUG='external-id',
+ HTTP_IN_PROGRESS='false')
+
+ # then
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ response_content = parse_xml(BytesIO(response.content))
+ deposit_id = response_content[
+ '{http://www.w3.org/2005/Atom}deposit_id']
+
+ deposit = Deposit.objects.get(pk=deposit_id)
+ dr = DepositRequest.objects.get(deposit=deposit)
+
+ self.assertIsNotNone(dr.metadata)
+ sw_version = dr.metadata.get(
+ '{https://doi.org/10.5063/SCHEMA/CODEMETA-2.0}softwareVersion')
+ self.assertEquals(sw_version, '10.4')
+
@istest
def post_deposit_atom_empty_body_request(self):
"""Posting empty body request should return a 400 response
"""
response = self.client.post(
reverse(COL_IRI, args=[self.collection.name]),
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data_empty_body)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@istest
def post_deposit_atom_badly_formatted_is_a_bad_request(self):
"""Posting a badly formatted atom should return a 400 response
"""
response = self.client.post(
reverse(COL_IRI, args=[self.collection.name]),
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data_badly_formatted)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@istest
def post_deposit_atom_without_slug_header_is_bad_request(self):
"""Posting an atom entry without a slug header should return a 400
"""
url = reverse(COL_IRI, args=[self.collection.name])
# when
response = self.client.post(
url,
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data0,
# + headers
HTTP_IN_PROGRESS='false')
self.assertIn(b'Missing SLUG header', response.content)
self.assertEqual(response.status_code,
status.HTTP_400_BAD_REQUEST)
@istest
def post_deposit_atom_unknown_collection(self):
"""Posting an atom entry to an unknown collection should return a 404
"""
response = self.client.post(
reverse(COL_IRI, args=['unknown-one']),
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data3,
HTTP_SLUG='something')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@istest
def post_deposit_atom_entry_initial(self):
"""Posting an initial atom entry should return 201 with deposit receipt
"""
# given
external_id = 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'
with self.assertRaises(Deposit.DoesNotExist):
Deposit.objects.get(external_id=external_id)
atom_entry_data = self.atom_entry_data0 % external_id.encode('utf-8')
# when
response = self.client.post(
reverse(COL_IRI, args=[self.collection.name]),
content_type='application/atom+xml;type=entry',
data=atom_entry_data,
HTTP_SLUG='external-id',
HTTP_IN_PROGRESS='false')
# then
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response_content = parse_xml(BytesIO(response.content))
deposit_id = response_content[
'{http://www.w3.org/2005/Atom}deposit_id']
deposit = Deposit.objects.get(pk=deposit_id)
self.assertEqual(deposit.collection, self.collection)
self.assertEqual(deposit.external_id, external_id)
self.assertEqual(deposit.status, DEPOSIT_STATUS_READY)
self.assertEqual(deposit.client, self.user)
# one associated request to a deposit
deposit_request = DepositRequest.objects.get(deposit=deposit)
self.assertIsNotNone(deposit_request.metadata)
self.assertFalse(bool(deposit_request.archive))
@istest
def post_deposit_atom_entry_with_codemeta(self):
"""Posting an initial atom entry should return 201 with deposit receipt
"""
# given
external_id = 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'
with self.assertRaises(Deposit.DoesNotExist):
Deposit.objects.get(external_id=external_id)
atom_entry_data = self.atom_entry_data_dc_codemeta % (
external_id.encode('utf-8'), )
# when
response = self.client.post(
reverse(COL_IRI, args=[self.collection.name]),
content_type='application/atom+xml;type=entry',
data=atom_entry_data,
HTTP_SLUG='external-id',
HTTP_IN_PROGRESS='false')
# then
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response_content = parse_xml(BytesIO(response.content))
deposit_id = response_content[
'{http://www.w3.org/2005/Atom}deposit_id']
deposit = Deposit.objects.get(pk=deposit_id)
self.assertEqual(deposit.collection, self.collection)
self.assertEqual(deposit.external_id, external_id)
self.assertEqual(deposit.status, 'ready')
self.assertEqual(deposit.client, self.user)
# one associated request to a deposit
deposit_request = DepositRequest.objects.get(deposit=deposit)
self.assertIsNotNone(deposit_request.metadata)
self.assertFalse(bool(deposit_request.archive))
@istest
def test_post_deposit_atom_entry_tei(self):
"""Posting initial atom entry as TEI should return 201 with receipt
"""
# given
external_id = 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'
with self.assertRaises(Deposit.DoesNotExist):
Deposit.objects.get(external_id=external_id)
atom_entry_data = self.atom_entry_tei
# when
response = self.client.post(
reverse(COL_IRI, args=[self.collection.name]),
content_type='application/atom+xml;type=entry',
data=atom_entry_data,
HTTP_SLUG=external_id,
HTTP_IN_PROGRESS='false')
# then
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response_content = parse_xml(BytesIO(response.content))
deposit_id = response_content[
'{http://www.w3.org/2005/Atom}deposit_id']
deposit = Deposit.objects.get(pk=deposit_id)
self.assertEqual(deposit.collection, self.collection)
self.assertEqual(deposit.external_id, external_id)
self.assertEqual(deposit.status, DEPOSIT_STATUS_READY)
self.assertEqual(deposit.client, self.user)
# one associated request to a deposit
deposit_request = DepositRequest.objects.get(deposit=deposit)
self.assertIsNotNone(deposit_request.metadata)
self.assertFalse(bool(deposit_request.archive))
@istest
def post_deposit_atom_entry_multiple_steps(self):
"""After initial deposit, updating a deposit should return a 201
"""
# given
external_id = 'urn:uuid:2225c695-cfb8-4ebb-aaaa-80da344efa6a'
with self.assertRaises(Deposit.DoesNotExist):
deposit = Deposit.objects.get(external_id=external_id)
# when
response = self.client.post(
reverse(COL_IRI, args=[self.collection.name]),
content_type='application/atom+xml;type=entry',
data=self.atom_entry_data1,
HTTP_IN_PROGRESS='True',
HTTP_SLUG=external_id)
# then
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response_content = parse_xml(BytesIO(response.content))
deposit_id = response_content[
'{http://www.w3.org/2005/Atom}deposit_id']
deposit = Deposit.objects.get(pk=deposit_id)
self.assertEqual(deposit.collection, self.collection)
self.assertEqual(deposit.external_id, external_id)
self.assertEqual(deposit.status, 'partial')
self.assertEqual(deposit.client, self.user)
# one associated request to a deposit
deposit_requests = DepositRequest.objects.filter(deposit=deposit)
self.assertEqual(len(deposit_requests), 1)
atom_entry_data = self.atom_entry_data2 % external_id.encode('utf-8')
update_uri = response._headers['location'][1]
# when updating the first deposit post
response = self.client.post(
update_uri,
content_type='application/atom+xml;type=entry',
data=atom_entry_data,
HTTP_IN_PROGRESS='False')
# then
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response_content = parse_xml(BytesIO(response.content))
deposit_id = response_content[
'{http://www.w3.org/2005/Atom}deposit_id']
deposit = Deposit.objects.get(pk=deposit_id)
self.assertEqual(deposit.collection, self.collection)
self.assertEqual(deposit.external_id, external_id)
self.assertEqual(deposit.status, DEPOSIT_STATUS_READY)
self.assertEqual(deposit.client, self.user)
self.assertEqual(len(Deposit.objects.all()), 1)
# now 2 associated requests to a same deposit
deposit_requests = DepositRequest.objects.filter(deposit=deposit)
self.assertEqual(len(deposit_requests), 2)
for deposit_request in deposit_requests:
actual_metadata = deposit_request.metadata
self.assertIsNotNone(actual_metadata)
self.assertFalse(bool(deposit_request.archive))