utils: add date type to endpoint parameters (#72641)

This commit is contained in:
Nicolas Roche 2022-12-18 17:44:48 +01:00
parent d2bcff79fb
commit 5d8b8b5bea
8 changed files with 92 additions and 78 deletions

View File

@ -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:

View File

@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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}

View File

@ -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': {

View File

@ -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

View File

@ -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', {}):

View File

@ -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'

View File

@ -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,

View File

@ -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(