utils: add date type to endpoint parameters (#72641)
This commit is contained in:
parent
d2bcff79fb
commit
5d8b8b5bea
|
@ -384,15 +384,12 @@ class Resource(BaseResource, HTTPResource):
|
||||||
},
|
},
|
||||||
'date_of_birth': {
|
'date_of_birth': {
|
||||||
'description': _('Beneficiary date of birth'),
|
'description': _('Beneficiary date of birth'),
|
||||||
|
'type': 'date',
|
||||||
'example_value': '1987-10-23',
|
'example_value': '1987-10-23',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def search(self, request, first_name, last_name, date_of_birth, NameID=None, commune_naissance=None):
|
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:
|
if date_of_birth.year < 1900:
|
||||||
raise APIError('date_of_birth must be >= 1900')
|
raise APIError('date_of_birth must be >= 1900')
|
||||||
if commune_naissance:
|
if commune_naissance:
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
|
||||||
import re
|
import re
|
||||||
from urllib import parse as urlparse
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
|
@ -183,6 +182,7 @@ class MDPH13Resource(BaseResource, HTTPResource):
|
||||||
},
|
},
|
||||||
'date_de_naissance': {
|
'date_de_naissance': {
|
||||||
'description': _('MDPH13 beneficiary date of birth'),
|
'description': _('MDPH13 beneficiary date of birth'),
|
||||||
|
'type': 'date',
|
||||||
'example_value': '1992-03-05',
|
'example_value': '1992-03-05',
|
||||||
},
|
},
|
||||||
'email': {
|
'email': {
|
||||||
|
@ -201,15 +201,17 @@ class MDPH13Resource(BaseResource, HTTPResource):
|
||||||
int(file_number)
|
int(file_number)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise APIError('numero_dossier must be a number', http_status=400)
|
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()
|
email = email.strip()
|
||||||
if not self.EMAIL_RE.match(email):
|
if not self.EMAIL_RE.match(email):
|
||||||
raise APIError('email is not valid', http_status=400)
|
raise APIError('email is not valid', http_status=400)
|
||||||
link, created, updated = Link.create_or_update(
|
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}
|
return {'link_id': link.pk, 'created': created, 'updated': updated}
|
||||||
|
|
||||||
|
|
|
@ -587,6 +587,7 @@ class PlanitechConnector(BaseResource):
|
||||||
},
|
},
|
||||||
'start_date': {
|
'start_date': {
|
||||||
'description': _('Start date'),
|
'description': _('Start date'),
|
||||||
|
'type': 'date',
|
||||||
'example_value': '2018-10-10',
|
'example_value': '2018-10-10',
|
||||||
},
|
},
|
||||||
'start_days': {
|
'start_days': {
|
||||||
|
@ -595,6 +596,7 @@ class PlanitechConnector(BaseResource):
|
||||||
},
|
},
|
||||||
'end_date': {
|
'end_date': {
|
||||||
'description': _('End date'),
|
'description': _('End date'),
|
||||||
|
'type': 'date',
|
||||||
'example_value': '2018-12-10',
|
'example_value': '2018-12-10',
|
||||||
},
|
},
|
||||||
'start_time': {
|
'start_time': {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.dateparse import parse_date
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
# make APIError available from this module
|
# make APIError available from this module
|
||||||
|
@ -208,6 +209,12 @@ class endpoint:
|
||||||
return 'integer'
|
return 'integer'
|
||||||
elif isinstance(value, float):
|
elif isinstance(value, float):
|
||||||
return 'float'
|
return 'float'
|
||||||
|
elif isinstance(value, str):
|
||||||
|
try:
|
||||||
|
if parse_date(value):
|
||||||
|
return 'date'
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
params = []
|
params = []
|
||||||
available_params = self.parameters
|
available_params = self.parameters
|
||||||
|
|
|
@ -34,6 +34,7 @@ from django.db.models import Q
|
||||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, resolve_url
|
from django.shortcuts import get_object_or_404, resolve_url
|
||||||
from django.urls import re_path, reverse
|
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.encoding import force_bytes, force_str
|
||||||
from django.utils.timezone import is_naive, make_aware
|
from django.utils.timezone import is_naive, make_aware
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
@ -386,6 +387,13 @@ class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View):
|
||||||
d[parameter] = float(d[parameter])
|
d[parameter] = float(d[parameter])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise InvalidParameterValue(parameter)
|
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:
|
if request.method == 'POST' and self.endpoint.endpoint_info.post:
|
||||||
request_body = self.endpoint.endpoint_info.post.get('request_body', {})
|
request_body = self.endpoint.endpoint_info.post.get('request_body', {})
|
||||||
if 'application/json' in request_body.get('schema', {}):
|
if 'application/json' in request_body.get('schema', {}):
|
||||||
|
|
|
@ -734,10 +734,13 @@ def test_endpoint_typed_params(app, db, monkeypatch):
|
||||||
'floating': {
|
'floating': {
|
||||||
'type': 'float',
|
'type': 'float',
|
||||||
},
|
},
|
||||||
|
'date': {
|
||||||
|
'type': 'date',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def httpcall(obj, request, boolean=False, integer=1, floating=1.1):
|
def httpcall(obj, request, boolean=False, integer=1, floating=1.1, date=None):
|
||||||
return {'boolean': boolean, 'integer': integer, 'floating': floating}
|
return {'boolean': boolean, 'integer': integer, 'floating': floating, 'date': date}
|
||||||
|
|
||||||
monkeypatch.setattr(StubInvoicesConnector, 'httpcall', httpcall, raising=False)
|
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'] == 1
|
||||||
assert json_res['err_desc'] == 'invalid value for parameter "floating"'
|
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):
|
def test_endpoint_params_type_detection(app, db, monkeypatch):
|
||||||
@endpoint(
|
@endpoint(
|
||||||
|
@ -794,6 +809,9 @@ def test_endpoint_params_type_detection(app, db, monkeypatch):
|
||||||
'float_by_example': {
|
'float_by_example': {
|
||||||
'example_value': 1.1,
|
'example_value': 1.1,
|
||||||
},
|
},
|
||||||
|
'date_by_example': {
|
||||||
|
'example_value': '1970-01-01',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def httpcall(
|
def httpcall(
|
||||||
|
@ -805,6 +823,7 @@ def test_endpoint_params_type_detection(app, db, monkeypatch):
|
||||||
bool_by_example=None,
|
bool_by_example=None,
|
||||||
int_by_example=None,
|
int_by_example=None,
|
||||||
float_by_example=None,
|
float_by_example=None,
|
||||||
|
date_by_example=None,
|
||||||
):
|
):
|
||||||
return {
|
return {
|
||||||
'boolean': boolean,
|
'boolean': boolean,
|
||||||
|
@ -813,6 +832,7 @@ def test_endpoint_params_type_detection(app, db, monkeypatch):
|
||||||
'bool_by_example': bool_by_example,
|
'bool_by_example': bool_by_example,
|
||||||
'int_by_example': int_by_example,
|
'int_by_example': int_by_example,
|
||||||
'float_by_example': float_by_example,
|
'float_by_example': float_by_example,
|
||||||
|
'date_by_example': date_by_example,
|
||||||
}
|
}
|
||||||
|
|
||||||
monkeypatch.setattr(StubInvoicesConnector, 'httpcall', httpcall, raising=False)
|
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
|
json_res = app.get('/stub-invoices/fake/httpcall?float_by_example=1.5').json
|
||||||
assert json_res['float_by_example'] == 1.5
|
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/')
|
res = app.get('/stub-invoices/fake/')
|
||||||
for param in res.pyquery('ul.get-params li'):
|
for param in res.pyquery('ul.get-params li'):
|
||||||
param_details = param.getchildren()
|
param_details = param.getchildren()
|
||||||
|
@ -847,6 +870,8 @@ def test_endpoint_params_type_detection(app, db, monkeypatch):
|
||||||
assert typ == 'integer'
|
assert typ == 'integer'
|
||||||
elif 'float' in name:
|
elif 'float' in name:
|
||||||
assert typ == 'float'
|
assert typ == 'float'
|
||||||
|
elif 'date' in name:
|
||||||
|
assert typ == 'date'
|
||||||
else:
|
else:
|
||||||
assert typ == 'string'
|
assert typ == 'string'
|
||||||
|
|
||||||
|
|
|
@ -272,17 +272,14 @@ def test_link_bad_file_number(mdph13):
|
||||||
assert str(e.value) == 'numero_dossier must be a number'
|
assert str(e.value) == 'numero_dossier must be a number'
|
||||||
|
|
||||||
|
|
||||||
def test_link_bad_date_de_naissance(mdph13):
|
def test_link_bad_date_de_naissance(app, mdph13):
|
||||||
with pytest.raises(APIError) as e:
|
url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s'
|
||||||
mdph13.link(
|
url = url % (NAME_ID, FILE_NUMBER, '34-45-6', None, None)
|
||||||
request=None,
|
response = app.post(url, status=400)
|
||||||
NameID=NAME_ID,
|
assert response.json['err_class'] == 'passerelle.views.InvalidParameterValue'
|
||||||
numero_dossier=FILE_NUMBER,
|
assert (
|
||||||
secret=None,
|
response.json['err_desc'] == 'invalid value for parameter "date_de_naissance (YYYY-MM-DD expected)"'
|
||||||
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_email(mdph13):
|
def test_link_bad_email(mdph13):
|
||||||
|
@ -298,74 +295,49 @@ def test_link_bad_email(mdph13):
|
||||||
assert str(e.value) == 'email is not valid'
|
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)
|
mock_http.add_response(DOSSIER_INCONNU)
|
||||||
with pytest.raises(APIError) as e:
|
url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s'
|
||||||
mdph13.link(
|
url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL)
|
||||||
request=None,
|
response = app.post(url)
|
||||||
NameID=NAME_ID,
|
assert response.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
|
||||||
numero_dossier=FILE_NUMBER,
|
assert response.json['err_desc'] == 'dossier-inconnu'
|
||||||
secret=SECRET,
|
|
||||||
date_de_naissance=DOB_ISOFORMAT,
|
|
||||||
email=EMAIL,
|
|
||||||
)
|
|
||||||
assert str(e.value) == '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)
|
mock_http.add_response(SECRET_INVALIDE)
|
||||||
with pytest.raises(APIError) as e:
|
url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s'
|
||||||
mdph13.link(
|
url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL)
|
||||||
request=None,
|
response = app.post(url)
|
||||||
NameID=NAME_ID,
|
assert response.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
|
||||||
numero_dossier=FILE_NUMBER,
|
assert response.json['err_desc'] == 'secret-invalide'
|
||||||
secret=SECRET,
|
|
||||||
date_de_naissance=DOB_ISOFORMAT,
|
|
||||||
email=EMAIL,
|
|
||||||
)
|
|
||||||
assert str(e.value) == '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'}}))
|
mock_http.add_response(json.dumps({'err': 0, 'data': {'numero': '456'}}))
|
||||||
with pytest.raises(APIError) as e:
|
url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s'
|
||||||
mdph13.link(
|
url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL)
|
||||||
request=None,
|
response = app.post(url)
|
||||||
NameID=NAME_ID,
|
assert response.json['err_class'] == 'passerelle.utils.jsonresponse.APIError'
|
||||||
numero_dossier=FILE_NUMBER,
|
assert response.json['err_desc'] == 'numero-must-match-numero-dossier'
|
||||||
secret=SECRET,
|
|
||||||
date_de_naissance=DOB_ISOFORMAT,
|
|
||||||
email=EMAIL,
|
|
||||||
)
|
|
||||||
assert str(e.value) == 'numero-must-match-numero-dossier'
|
|
||||||
|
|
||||||
|
|
||||||
def test_link_ok(mdph13, mock_http):
|
def test_link_ok(app, mdph13, mock_http):
|
||||||
# check first time link
|
# check first time link
|
||||||
mock_http.add_response(VALID_RESPONSE)
|
mock_http.add_response(VALID_RESPONSE)
|
||||||
assert not Link.objects.count()
|
assert not Link.objects.count()
|
||||||
response = mdph13.link(
|
url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s&ip=%s'
|
||||||
request=None,
|
url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET, EMAIL, IP)
|
||||||
NameID=NAME_ID,
|
response = app.post(url)
|
||||||
numero_dossier=FILE_NUMBER,
|
|
||||||
secret=SECRET,
|
|
||||||
date_de_naissance=DOB_ISOFORMAT,
|
|
||||||
email=EMAIL,
|
|
||||||
ip=IP,
|
|
||||||
)
|
|
||||||
link = Link.objects.get()
|
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
|
# check relinking with update
|
||||||
mock_http.add_response(VALID_RESPONSE)
|
mock_http.add_response(VALID_RESPONSE)
|
||||||
response = mdph13.link(
|
url = '/mdph13/test1/link/?NameID=%s&numero_dossier=%s&date_de_naissance=%s&secret=%s&email=%s&ip=%s'
|
||||||
request=None,
|
url = url % (NAME_ID, FILE_NUMBER, DOB_ISOFORMAT, SECRET + 'a', EMAIL, IP)
|
||||||
NameID=NAME_ID,
|
response = app.post(url)
|
||||||
numero_dossier=FILE_NUMBER,
|
assert response.json == {
|
||||||
secret=SECRET + 'a',
|
'err': 0,
|
||||||
date_de_naissance=DOB_ISOFORMAT,
|
|
||||||
email=EMAIL,
|
|
||||||
)
|
|
||||||
assert response == {
|
|
||||||
'link_id': link.pk,
|
'link_id': link.pk,
|
||||||
'created': False,
|
'created': False,
|
||||||
'updated': True,
|
'updated': True,
|
||||||
|
|
|
@ -992,11 +992,12 @@ def test_get_freegaps(app, connector, monkeypatch, settings):
|
||||||
# bad date format
|
# bad date format
|
||||||
response = app.get(
|
response = app.get(
|
||||||
'/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
|
'/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
|
json_resp = response.json
|
||||||
assert json_resp['err'] == 1
|
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
|
# bad time format
|
||||||
response = app.get(
|
response = app.get(
|
||||||
|
|
Loading…
Reference in New Issue