diff --git a/passerelle/apps/atos_genesys/models.py b/passerelle/apps/atos_genesys/models.py index 9c905728..b86816f8 100644 --- a/passerelle/apps/atos_genesys/models.py +++ b/passerelle/apps/atos_genesys/models.py @@ -384,15 +384,12 @@ class Resource(BaseResource, HTTPResource): }, 'date_of_birth': { 'description': _('Beneficiary date of birth'), + 'type': 'date', 'example_value': '1987-10-23', }, }, ) def search(self, request, first_name, last_name, date_of_birth, NameID=None, commune_naissance=None): - try: - date_of_birth = datetime.datetime.strptime(date_of_birth, '%Y-%m-%d').date() - except (ValueError, TypeError): - raise APIError('invalid date_of_birth: %r' % date_of_birth) if date_of_birth.year < 1900: raise APIError('date_of_birth must be >= 1900') if commune_naissance: diff --git a/passerelle/contrib/mdph13/models.py b/passerelle/contrib/mdph13/models.py index 7ce48c01..7a79b8c3 100644 --- a/passerelle/contrib/mdph13/models.py +++ b/passerelle/contrib/mdph13/models.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import base64 -import datetime import re from urllib import parse as urlparse @@ -183,6 +182,7 @@ class MDPH13Resource(BaseResource, HTTPResource): }, 'date_de_naissance': { 'description': _('MDPH13 beneficiary date of birth'), + 'type': 'date', 'example_value': '1992-03-05', }, 'email': { @@ -201,15 +201,17 @@ class MDPH13Resource(BaseResource, HTTPResource): int(file_number) except ValueError: raise APIError('numero_dossier must be a number', http_status=400) - try: - dob = datetime.datetime.strptime(date_de_naissance.strip(), '%Y-%m-%d').date() - except ValueError: - raise APIError('date_de_naissance must be a date YYYY-MM-DD', http_status=400) email = email.strip() if not self.EMAIL_RE.match(email): raise APIError('email is not valid', http_status=400) link, created, updated = Link.create_or_update( - resource=self, NameID=NameID, file_number=file_number, secret=secret, dob=dob, email=email, ip=ip + resource=self, + NameID=NameID, + file_number=file_number, + secret=secret, + dob=date_de_naissance, + email=email, + ip=ip, ) return {'link_id': link.pk, 'created': created, 'updated': updated} diff --git a/passerelle/contrib/planitech/models.py b/passerelle/contrib/planitech/models.py index 752978c7..d1c2b6ee 100644 --- a/passerelle/contrib/planitech/models.py +++ b/passerelle/contrib/planitech/models.py @@ -587,6 +587,7 @@ class PlanitechConnector(BaseResource): }, 'start_date': { 'description': _('Start date'), + 'type': 'date', 'example_value': '2018-10-10', }, 'start_days': { @@ -595,6 +596,7 @@ class PlanitechConnector(BaseResource): }, 'end_date': { 'description': _('End date'), + 'type': 'date', 'example_value': '2018-12-10', }, 'start_time': { diff --git a/passerelle/utils/api.py b/passerelle/utils/api.py index 28c188e1..6746b059 100644 --- a/passerelle/utils/api.py +++ b/passerelle/utils/api.py @@ -18,6 +18,7 @@ import inspect from django.urls import reverse +from django.utils.dateparse import parse_date from django.utils.safestring import mark_safe # make APIError available from this module @@ -208,6 +209,12 @@ class endpoint: return 'integer' elif isinstance(value, float): return 'float' + elif isinstance(value, str): + try: + if parse_date(value): + return 'date' + except ValueError: + pass params = [] available_params = self.parameters diff --git a/passerelle/views.py b/passerelle/views.py index 5ebb24f2..4e596041 100644 --- a/passerelle/views.py +++ b/passerelle/views.py @@ -34,6 +34,7 @@ from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, resolve_url from django.urls import re_path, reverse +from django.utils.dateparse import parse_date from django.utils.encoding import force_bytes, force_str from django.utils.timezone import is_naive, make_aware from django.utils.translation import gettext_lazy as _ @@ -386,6 +387,13 @@ class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View): d[parameter] = float(d[parameter]) except ValueError: raise InvalidParameterValue(parameter) + elif parameter_info.get('type') == 'date': + try: + d[parameter] = parse_date(d[parameter].strip()) + except ValueError: + raise InvalidParameterValue('%s (not a valid date)' % parameter) + if not d[parameter]: + raise InvalidParameterValue('%s (YYYY-MM-DD expected)' % parameter) if request.method == 'POST' and self.endpoint.endpoint_info.post: request_body = self.endpoint.endpoint_info.post.get('request_body', {}) if 'application/json' in request_body.get('schema', {}): diff --git a/tests/test_generic_endpoint.py b/tests/test_generic_endpoint.py index 4d43d8a7..ff15bf89 100644 --- a/tests/test_generic_endpoint.py +++ b/tests/test_generic_endpoint.py @@ -734,10 +734,13 @@ def test_endpoint_typed_params(app, db, monkeypatch): 'floating': { 'type': 'float', }, + 'date': { + 'type': 'date', + }, }, ) - def httpcall(obj, request, boolean=False, integer=1, floating=1.1): - return {'boolean': boolean, 'integer': integer, 'floating': floating} + def httpcall(obj, request, boolean=False, integer=1, floating=1.1, date=None): + return {'boolean': boolean, 'integer': integer, 'floating': floating, 'date': date} monkeypatch.setattr(StubInvoicesConnector, 'httpcall', httpcall, raising=False) @@ -780,6 +783,18 @@ def test_endpoint_typed_params(app, db, monkeypatch): assert json_res['err'] == 1 assert json_res['err_desc'] == 'invalid value for parameter "floating"' + json_res = app.get('/stub-invoices/fake/httpcall?date=1970-01-01').json + assert json_res['date'] == '1970-01-01' + json_res = app.get('/stub-invoices/fake/httpcall?date=nodate', status=400).json + assert json_res['err'] == 1 + assert json_res['err_desc'] == 'invalid value for parameter "date (YYYY-MM-DD expected)"' + json_res = app.get('/stub-invoices/fake/httpcall?date=', status=400).json + assert json_res['err'] == 1 + assert json_res['err_desc'] == 'invalid value for parameter "date (YYYY-MM-DD expected)"' + json_res = app.get('/stub-invoices/fake/httpcall?date=1970-02-31', status=400).json + assert json_res['err'] == 1 + assert json_res['err_desc'] == 'invalid value for parameter "date (not a valid date)"' + def test_endpoint_params_type_detection(app, db, monkeypatch): @endpoint( @@ -794,6 +809,9 @@ def test_endpoint_params_type_detection(app, db, monkeypatch): 'float_by_example': { 'example_value': 1.1, }, + 'date_by_example': { + 'example_value': '1970-01-01', + }, }, ) def httpcall( @@ -805,6 +823,7 @@ def test_endpoint_params_type_detection(app, db, monkeypatch): bool_by_example=None, int_by_example=None, float_by_example=None, + date_by_example=None, ): return { 'boolean': boolean, @@ -813,6 +832,7 @@ def test_endpoint_params_type_detection(app, db, monkeypatch): 'bool_by_example': bool_by_example, 'int_by_example': int_by_example, 'float_by_example': float_by_example, + 'date_by_example': date_by_example, } monkeypatch.setattr(StubInvoicesConnector, 'httpcall', httpcall, raising=False) @@ -835,6 +855,9 @@ def test_endpoint_params_type_detection(app, db, monkeypatch): json_res = app.get('/stub-invoices/fake/httpcall?float_by_example=1.5').json assert json_res['float_by_example'] == 1.5 + json_res = app.get('/stub-invoices/fake/httpcall?date_by_example=1970-01-01').json + assert json_res['date_by_example'] == '1970-01-01' + res = app.get('/stub-invoices/fake/') for param in res.pyquery('ul.get-params li'): param_details = param.getchildren() @@ -847,6 +870,8 @@ def test_endpoint_params_type_detection(app, db, monkeypatch): assert typ == 'integer' elif 'float' in name: assert typ == 'float' + elif 'date' in name: + assert typ == 'date' else: assert typ == 'string' diff --git a/tests/test_mdph13.py b/tests/test_mdph13.py index 0529e96c..c9cbe868 100644 --- a/tests/test_mdph13.py +++ b/tests/test_mdph13.py @@ -272,17 +272,14 @@ def test_link_bad_file_number(mdph13): assert str(e.value) == 'numero_dossier must be a number' -def test_link_bad_date_de_naissance(mdph13): - with pytest.raises(APIError) as e: - mdph13.link( - request=None, - NameID=NAME_ID, - numero_dossier=FILE_NUMBER, - secret=None, - date_de_naissance='34-45-6', - email=None, - ) - assert str(e.value) == 'date_de_naissance must be a date YYYY-MM-DD' +def test_link_bad_date_de_naissance(app, mdph13): + url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s' + url = url % (NAME_ID, FILE_NUMBER, '34-45-6', None, None) + response = app.post(url, status=400) + assert response.json['err_class'] == 'passerelle.views.InvalidParameterValue' + assert ( + response.json['err_desc'] == 'invalid value for parameter "date_de_naissance (YYYY-MM-DD expected)"' + ) def test_link_bad_email(mdph13): @@ -298,74 +295,49 @@ def test_link_bad_email(mdph13): assert str(e.value) == 'email is not valid' -def test_link_nok_dossier_inconnu(mdph13, mock_http): +def test_link_nok_dossier_inconnu(app, mdph13, mock_http): mock_http.add_response(DOSSIER_INCONNU) - with pytest.raises(APIError) as e: - mdph13.link( - request=None, - NameID=NAME_ID, - numero_dossier=FILE_NUMBER, - secret=SECRET, - date_de_naissance=DOB_ISOFORMAT, - email=EMAIL, - ) - assert str(e.value) == 'dossier-inconnu' + url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s' + url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL) + response = app.post(url) + assert response.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' + assert response.json['err_desc'] == 'dossier-inconnu' -def test_link_nok_secret_invalide(mdph13, mock_http): +def test_link_nok_secret_invalide(app, mdph13, mock_http): mock_http.add_response(SECRET_INVALIDE) - with pytest.raises(APIError) as e: - mdph13.link( - request=None, - NameID=NAME_ID, - numero_dossier=FILE_NUMBER, - secret=SECRET, - date_de_naissance=DOB_ISOFORMAT, - email=EMAIL, - ) - assert str(e.value) == 'secret-invalide' + url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s' + url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL) + response = app.post(url) + assert response.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' + assert response.json['err_desc'] == 'secret-invalide' -def test_link_numero_dont_match(mdph13, mock_http): +def test_link_numero_dont_match(app, mdph13, mock_http): mock_http.add_response(json.dumps({'err': 0, 'data': {'numero': '456'}})) - with pytest.raises(APIError) as e: - mdph13.link( - request=None, - NameID=NAME_ID, - numero_dossier=FILE_NUMBER, - secret=SECRET, - date_de_naissance=DOB_ISOFORMAT, - email=EMAIL, - ) - assert str(e.value) == 'numero-must-match-numero-dossier' + url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s' + url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL) + response = app.post(url) + assert response.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' + assert response.json['err_desc'] == 'numero-must-match-numero-dossier' -def test_link_ok(mdph13, mock_http): +def test_link_ok(app, mdph13, mock_http): # check first time link mock_http.add_response(VALID_RESPONSE) assert not Link.objects.count() - response = mdph13.link( - request=None, - NameID=NAME_ID, - numero_dossier=FILE_NUMBER, - secret=SECRET, - date_de_naissance=DOB_ISOFORMAT, - email=EMAIL, - ip=IP, - ) + url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s&ip=%s' + url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL, IP) + response = app.post(url) link = Link.objects.get() - assert response == {'link_id': link.pk, 'created': True, 'updated': False} + assert response.json == {'err': 0, 'link_id': link.pk, 'created': True, 'updated': False} # check relinking with update mock_http.add_response(VALID_RESPONSE) - response = mdph13.link( - request=None, - NameID=NAME_ID, - numero_dossier=FILE_NUMBER, - secret=SECRET + 'a', - date_de_naissance=DOB_ISOFORMAT, - email=EMAIL, - ) - assert response == { + url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s&ip=%s' + url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET + 'a', EMAIL, IP) + response = app.post(url) + assert response.json == { + 'err': 0, 'link_id': link.pk, 'created': False, 'updated': True, diff --git a/tests/test_planitech.py b/tests/test_planitech.py index 1217f842..12595eec 100644 --- a/tests/test_planitech.py +++ b/tests/test_planitech.py @@ -992,11 +992,12 @@ def test_get_freegaps(app, connector, monkeypatch, settings): # bad date format response = app.get( '/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00' - '&start_date=notadate&display=place' + '&start_date=notadate&display=place', + status=400, ) json_resp = response.json assert json_resp['err'] == 1 - assert json_resp['err_desc'] == "Invalid date format: notadate" + assert json_resp['err_desc'] == 'invalid value for parameter "start_date (YYYY-MM-DD expected)"' # bad time format response = app.get(