passerelle/tests/test_planitech.py

559 lines
22 KiB
Python

from datetime import datetime
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache
from httmock import urlmatch, HTTMock
import mock
import pytest
import requests
from passerelle.base.models import ApiUser, AccessRight
from passerelle.contrib.planitech import mste
from passerelle.contrib.planitech.models import PlanitechConnector, Pairing
from passerelle.utils.jsonresponse import APIError
def assert_mste(data, ref_data):
""" skip CRC verification
"""
assert len(data) == len(ref_data)
for i in range(len(data)):
if i != 2:
assert data[i] == ref_data[i]
@pytest.mark.parametrize("data,mste_data", [
(None, ["MSTE0102", 6, "CRC82413E70", 0, 0, 0]),
("toto", ["MSTE0102", 7, "CRCD45ACB10", 0, 0, 21, "toto"]), # string
(mste.Couple(("toto", "tata")), ["MSTE0102", 10, "CRCD45ACB10", 0, 0, 32, 21, "toto", 21,
"tata"]),
# couple
([mste.Couple(("toto", "tata")), mste.Couple(("toto", "tata"))],
["MSTE0102", 14, "CRCD45ACB10", 0, 0, 31, 2, 32, 21, "toto", 21, "tata", 9, 1]),
# couple are stored in refs
(["toto"], ["MSTE0102", 9, "CRCD4E14B75", 0, 0, 31, 1, 21, "toto"]), # array
(["toto", "tata", "toto"], ["MSTE0102", 13, "CRC7311752F", 0, 0, 31, 3, 21, "toto", 21,
"tata", 9, 1]), # array with reference
({"mykey": "toto"}, ["MSTE0102", 11, "CRC1C9E9FE1", 0, 1, "mykey", 30, 1, 0, 21, "toto"]),
# dictionnary
([{"mykey": "toto"}, {"mykey": "toto"}],
["MSTE0102", 15, "CRC1C9E9FE1", 0, 1, "mykey", 31, 2, 30, 1, 0, 21,
"toto", 9, 1]),
# dictionnary are stored in refs
(float(2), ["MSTE0102", 7, "CRC1C9E9FE1", 0, 0, 20, 2]), # decimal
([float(2), float(2)], ["MSTE0102", 11, "CRC1C9E9FE1", 0, 0, 31, 2, 20, 2, 9, 1]),
# decimal are stored in refs
(mste.Uint32(1), ["MSTE0102", 7, "CRC1C9E9FE1", 0, 0, 15, 1]), # uint32
(True, ["MSTE0102", 6, "CRC1C9E9FE1", 0, 0, 1]), # True
(False, ["MSTE0102", 6, "CRC1C9E9FE1", 0, 0, 2]), # False
('', ["MSTE0102", 6, "CRC1C9E9FE1", 0, 0, 3]), # empty string
(datetime.fromtimestamp(1537364340), ["MSTE0102", 7, "CRC1C9E9FE1", 0, 0, 22, 1537364340]),
# local date
([datetime.fromtimestamp(1537364340), datetime.fromtimestamp(1537364340)],
["MSTE0102", 11, "CRC1C9E9FE1", 0, 0, 31, 2, 22, 1537364340, 9, 1]),
# local date in refs
])
def test_mste(mste_data, data):
assert data == mste.decode(mste_data)
assert_mste(mste.encode(data), mste_data)
def test_encode_unsupported_type():
with pytest.raises(TypeError):
mste.encode(set())
def test_real():
mste_data = ["MSTE0102", 128, "CRC99D9BCEB", 0, 11, "requestDate", "responseDate", "requestName", "requestedEndingTime", "availablePlaces", "label", "freeGaps", "placeIdentifier", "resourceIdentifier", "daysMask", "requestedStartingTime", 30, 7, 0, 22, 1538404500, 1, 22, 1538404500, 2, 21, "getFreeGaps", 3, 20, 600, 4, 31, 1, 30, 4, 5, 21, "M.F.F. 2", 6, 31, 15, 32, 22, 1538384400, 22, 1538388000, 32, 22, 1538470800, 22, 1538474400, 32, 22, 1538557200, 22, 1538560800, 32, 22, 1538643600, 22, 1538647200, 32, 22, 1538989200, 22, 1538992800, 32, 22, 1539075600, 22, 1539079200, 32, 22, 1539162000, 22, 1539165600, 32, 22, 1539248400, 22, 1539252000, 32, 22, 1539334800, 22, 1539338400, 32, 22, 1539507600, 22, 1539511200, 32, 22, 1539594000, 22, 1539597600, 32, 22, 1539680400, 22, 1539684000, 32, 22, 1539766800, 22, 1539770400, 32, 22, 1539853200, 22, 1539856800, 32, 22, 1539939600, 22, 1539943200, 7, 20, 2, 8, 9, 54, 9, 20, 127, 10, 20, 540]
mste.decode(mste_data)
@pytest.fixture()
def connector(db):
api = ApiUser.objects.create(username='all', keytype='', key='')
connector = PlanitechConnector.objects.create(
url='http://example.planitech.com/', username='admin', password='admin',
verify_cert=False, slug='slug-planitech',
custom_fields=[{'name': 'some_custom_field', 'type': 'string'}])
obj_type = ContentType.objects.get_for_model(connector)
AccessRight.objects.create(
codename='can_access', apiuser=api, resource_type=obj_type, resource_pk=connector.pk)
return connector
def mock_planitech(monkeypatch, return_value=None, side_effect=None, referential=None):
from passerelle.contrib.planitech import models
monkeypatch.setattr(models.PlanitechConnector, '_login', mock.Mock())
kwargs = {}
if return_value is not None:
kwargs['return_value'] = return_value
if side_effect is not None:
kwargs['side_effect'] = side_effect
mock_call_planitech = mock.Mock(**kwargs)
monkeypatch.setattr(models.PlanitechConnector, '_call_planitech', mock_call_planitech)
if referential is not None:
mock_get_referential = mock.Mock(return_value=referential)
monkeypatch.setattr(
models.PlanitechConnector, '_get_places_referential', mock_get_referential)
return mock_call_planitech
def test_call_planitech(connector, monkeypatch):
class MockResponse(object):
status_code = 200
content = None
def __init__(self, content=None, status_code=None):
if content is not None:
self.content = content
if status_code is not None:
self.status_code = status_code
def session_meth(self, *args, **kwargs):
return self
def json(self):
return mste.encode(self.content)
connector._planitech_session = True
response = MockResponse(content='somestring')
assert connector._call_planitech(response.session_meth, 'endpoint') == "somestring"
response = MockResponse(content=set(), status_code=400)
with pytest.raises(APIError) as excinfo:
connector._call_planitech(response.session_meth, 'endpoint')
assert str(excinfo.value) == 'Planitech error 400'
response = MockResponse(content='unexpected error format', status_code=400)
with pytest.raises(APIError) as excinfo:
connector._call_planitech(response.session_meth, 'endpoint')
assert str(excinfo.value) == 'Planitech error 400'
response = MockResponse(content={'errors': 'planitech error message'}, status_code=400)
with pytest.raises(APIError) as excinfo:
connector._call_planitech(response.session_meth, 'endpoint')
assert str(excinfo.value) == 'Planitech error 400 - planitech error message'
def test_create_reservation(app, connector, monkeypatch):
side_effect = [
{
'creationStatus': 'OK',
'externalUserIdentifier': '22b9c0d91fdc4f379d1356a4aaa9d38b',
'requestDate': datetime(2019, 1, 9, 15, 41),
'requestName': 'createPerson',
'responseDate': datetime(2019, 1, 9, 15, 41),
'userIdentifier': 13826.0
},
{
'creationStatus': 'OK',
'reservationIdentifier': 1
}
]
mock_call_planitech = mock_planitech(monkeypatch, side_effect=side_effect)
params = {
'date': '2018-11-11',
'start_time': '10:00',
'end_time': '11:00',
'place_id': 1,
'price': 10,
'name_id': 'john-doe',
'first_name': 'jon',
'last_name': 'doe',
'email': 'jon.doe@localhost',
'activity_id': 1,
'object': 'reservation object',
'type_id': 1,
'vat_rate': 1,
}
response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
json_resp = response.json
assert json_resp['err'] == 0
assert json_resp['data']['reservation_id'] == 1
person_args = mock_call_planitech.call_args_list[0][0]
assert person_args[1] == 'createPerson'
person_args = person_args[2]
assert person_args['mail'] == 'jon.doe@localhost'
assert person_args['firstName'] == 'jon'
assert person_args['name'] == 'doe'
external_id = person_args['externalUserIdentifier']
pairing = Pairing.objects.get(resource=connector, name_id='john-doe')
assert pairing.external_id == external_id
reservation_args = mock_call_planitech.call_args_list[1][0]
assert reservation_args[1] == 'createReservation'
reservation_args = reservation_args[2]
assert reservation_args['start'] == datetime(2018, 11, 11, 10, 0)
assert reservation_args['end'] == datetime(2018, 11, 11, 11, 0)
assert reservation_args['places'] == [1]
assert reservation_args['price'] == 10
assert reservation_args['contractorExternalIdentifier'] == pairing.external_id
assert reservation_args['activityID'] == 1
assert reservation_args['object'] == 'reservation object'
assert reservation_args['typeID'] == 1
assert reservation_args['vatRate'] == 1
# Second reservation for same user : no planitech person created
mock_call_planitech = mock_planitech(monkeypatch, return_value=side_effect[1])
response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
json_resp = response.json
assert json_resp['err'] == 0
assert json_resp['data']['reservation_id'] == 1
reservation_args = mock_call_planitech.call_args_list[0][0]
assert reservation_args[1] == 'createReservation'
assert Pairing.objects.count() == 1
# Create reservation failed
mock_call_planitech = mock_planitech(
monkeypatch, return_value={
'creationStatus': 'NOTOK',
'reservationIdentifier': 1
})
response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == 'Reservation creation failed: NOTOK'
# Create reservation failed - nor reservation ID
mock_call_planitech = mock_planitech(
monkeypatch, return_value={
'creationStatus': 'OK'
})
response = app.post_json('/planitech/slug-planitech/createreservation', params=params)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == 'Reservation creation failed: no reservation ID'
def test_getplaces_referential(app, connector, monkeypatch):
side_effect = [
{
'placesList': [
{'identifier': 1.0, 'label': 'salle 1'},
{'identifier': 2.0, 'label': 'salle 2'}
]
},
{
'requestedPlaces': [
{
'identifier': 1.0, 'capacity': 10.0,
'streetNumber': 1, 'address1': 'rue planitech',
'city': 'thecity', 'zipCode': '00000'
},
{
'identifier': 2.0, 'capacity': 20.0,
'some_custom_field': 'Yes'
}
]
}
]
mock_planitech(monkeypatch, side_effect=side_effect)
response = app.get('/planitech/slug-planitech/getplacesreferential')
expected_res = {
'2': {
u'capacity': 20, u'label': u'salle 2', u'identifier': 2,
'street_number': None, 'address': None,
'city': None, 'zipcode': None, 'some_custom_field': 'Yes'
},
'1': {
u'capacity': 10, u'label': u'salle 1', u'identifier': 1,
'street_number': 1, 'address': 'rue planitech',
'city': 'thecity', 'zipcode': '00000', 'some_custom_field': None
}
}
assert response.json['data'] == expected_res
mock_planitech(monkeypatch, side_effect=side_effect)
response = app.get(
'/planitech/slug-planitech/getplacesreferential?referential_some_custom_field=Yes')
assert response.json['data'] == {
'2': {
u'capacity': 20, u'label': u'salle 2', u'identifier': 2,
'street_number': None, 'address': None,
'city': None, 'zipcode': None, 'some_custom_field': 'Yes'
}
}
def test_getplaces_referential_use_cache(app, connector):
cache_key = 'planitech-%s-places' % connector.id
cache.set(cache_key, {'some': 'data'})
response = app.get('/planitech/slug-planitech/getplacesreferential')
assert response.json_body['data'] == {'some': 'data'}
cache.delete(cache_key)
def test_login(connector):
@urlmatch(netloc=r'(.*\.)?planitech\.com$')
def planitech_mock(url, request):
raise requests.exceptions.RequestException("Bad news")
with HTTMock(planitech_mock):
with pytest.raises(APIError) as excinfo:
connector._login()
assert str(excinfo.value) == 'Authentication to Planitech failed: Bad news'
def test_update_reservation(app, connector, monkeypatch):
mock_call_planitech = mock_planitech(
monkeypatch, return_value={'modificationStatus': 'OK'}
)
response = app.post_json(
'/planitech/slug-planitech/updatereservation',
params={'status': 'confirmed', 'reservation_id': 1}
)
json_resp = response.json
assert json_resp['err'] == 0
assert json_resp['data']['raw_data'] == {'modificationStatus': 'OK'}
call_params = mock_call_planitech.call_args[0][2]
assert call_params['reservationIdentifier'] == 1
assert call_params['situation'] == 3
# Update failed
mock_planitech(
monkeypatch, return_value={'modificationStatus': 'NOTOK'}
)
response = app.post_json(
'/planitech/slug-planitech/updatereservation',
params={'status': 'confirmed', 'reservation_id': 1}
)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == 'Update reservation failed: NOTOK'
# Connector bad param
response = app.post_json(
'/planitech/slug-planitech/updatereservation', params={'status': 'confirmed'}, status=400)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == "'reservation_id' is a required property"
def freegaps_data():
referential = {
2.0: {
u'capacity': 20.0, u'label': u'salle 2', u'identifier': 2.0,
u'some_custom_field': u'Yes'
},
1.0: {
u'capacity': 10.0, u'label': u'salle 1', u'identifier': 1.0
}
}
place1_gaps = [
[datetime(year=2018, month=11, day=11, hour=10, minute=0),
datetime(year=2018, month=11, day=11, hour=11, minute=0)],
[datetime(year=2018, month=11, day=12, hour=10, minute=0),
datetime(year=2018, month=11, day=12, hour=11, minute=0)]
]
place2_gaps = [
[datetime(year=2018, month=11, day=11, hour=10, minute=0),
datetime(year=2018, month=11, day=11, hour=11, minute=0)],
[datetime(year=2018, month=11, day=12, hour=10, minute=0),
datetime(year=2018, month=11, day=12, hour=11, minute=0)],
[datetime(year=2018, month=11, day=13, hour=10, minute=0),
datetime(year=2018, month=11, day=13, hour=11, minute=0)]
]
planitech_r = {
'availablePlaces': [
{
'placeIdentifier': 1.0,
'freeGaps': place1_gaps
},
{
'placeIdentifier': 2.0,
'freeGaps': place2_gaps
}
]
}
return referential, planitech_r
def test_get_freegaps(app, connector, monkeypatch, settings):
settings.LANGUAGE_CODE = 'fr-fr'
referential, planitech_r = freegaps_data()
mock_call_planitech = mock_planitech(
monkeypatch, return_value=planitech_r, referential=referential)
# date display
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
'&start_date=2018-11-11&display=date'
)
assert response.json['data'] == [
{
u'id': u'2018-11-11', u'text': u'dimanche 11 novembre 2018',
u'short_text': '11/11/2018'
},
{
u'id': u'2018-11-12', u'text': u'lundi 12 novembre 2018',
u'short_text': '12/11/2018'
},
{
u'id': u'2018-11-13', u'text': u'mardi 13 novembre 2018',
u'short_text': '13/11/2018'
}
]
# place display
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
'&start_date=2018-11-11&display=place'
)
assert response.json['data'] == [
{u'id': 1.0, u'text': u'salle 1'},
{u'id': 2.0, u'text': u'salle 2'}
]
# full display
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
'&start_date=2018-11-11&display=full'
)
res = response.json['data']
assert 'place' in res
assert 'date' in res
full = res['full']
place_1 = full[0]
assert place_1['id'] == 1.0
assert place_1['text'] == u'salle 1'
assert place_1['dates'] == [
{u'available': True, u'id': u'2018-11-11'},
{u'available': True, u'id': u'2018-11-12'},
{u'available': False, u'id': u'2018-11-13'}
]
place_2 = full[1]
assert place_2['id'] == 2.0
assert place_2['text'] == u'salle 2'
assert place_2['dates'] == [
{u'available': True, u'id': u'2018-11-11'},
{u'available': True, u'id': u'2018-11-12'},
{u'available': True, u'id': u'2018-11-13'}
]
# general params interpretation
mock_call_planitech.reset_mock()
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
'&start_date=2018-11-11&end_date=2018-11-12&display=date'
)
call_params = mock_call_planitech.call_args[0][2]
assert call_params['startingDate'] == datetime(2018, 11, 11, 10, 0)
assert call_params['endingDate'] == datetime(2018, 11, 12, 00, 0)
assert call_params['placeIdentifiers'] == [1.0, 2.0]
assert call_params['requestedStartingTime'] == 0.0 # means startingDate
assert call_params['requestedEndingTime'] == 60.0 # means startingDate + 60 minutes
assert call_params['reservationDays'] == [0, 6] # means every day of the week
# no end date means only the starting date is interesting
# so ending date is the day after at midnight
# which gives only results for starting date
mock_call_planitech.reset_mock()
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
'&start_date=2018-11-11&display=place'
)
call_params = mock_call_planitech.call_args[0][2]
assert call_params['startingDate'] == datetime(2018, 11, 11, 10, 0)
assert call_params['endingDate'] == datetime(2018, 11, 12, 0, 0)
# capacity used against referential to restrict placeIdentifiers
mock_call_planitech.reset_mock()
app.get(
'/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
'&start_date=2018-11-11&max_capacity=15&display=place'
)
call_params = mock_call_planitech.call_args[0][2]
assert call_params['placeIdentifiers'] == [1.0]
# place_id parameter override capacity
mock_call_planitech.reset_mock()
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
'&start_date=2018-11-11&max_capacity=15&place_id=2&display=place'
)
call_params = mock_call_planitech.call_args[0][2]
assert call_params['placeIdentifiers'] == [2.0]
# weekdays means no week days exclusion
mock_call_planitech.reset_mock()
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
'&start_date=2018-11-11&max_capacity=15&place_id=2&display=place'
'&weekdays=true'
)
call_params = mock_call_planitech.call_args[0][2]
assert 'reservationDays' not in call_params
# custom field restriction
mock_call_planitech.reset_mock()
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
'&start_date=2018-11-11&referential_some_custom_field=Yes&display=place'
)
call_params = mock_call_planitech.call_args[0][2]
assert call_params['placeIdentifiers'] == [2.0]
# BAD REQUEST
# bad date format
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=11:00&&end_time=14:00'
'&start_date=notadate&display=place'
)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == "Invalid date format: notadate"
# bad time format
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=notatime&&end_time=14:00'
'&start_date=2018-11-11&display=place'
)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == "Invalid time format: notatime"
# start_date or start_days required
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=notatime&&end_time=14:00'
'&display=place'
)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == "start_date or start_days is required"
# invalid display param
response = app.get(
'/planitech/slug-planitech/getfreegaps?start_time=notatime&&end_time=14:00'
'&start_date=2018-11-11&display=unkown'
)
json_resp = response.json
assert json_resp['err'] == 1
assert json_resp['err_desc'] == "Valid display are: date, place, full"
def test_get_freegaps_start_days(app, connector, monkeypatch, settings, freezer):
freezer.move_to('2018-11-09 23:50:00')
settings.LANGUAGE_CODE = 'fr-fr'
referential, planitech_r = freegaps_data()
mock_call_planitech = mock_planitech(
monkeypatch, return_value=planitech_r, referential=referential)
# starting_date and ending_date can be specified as days delta from now
app.get(
'/planitech/slug-planitech/getfreegaps?start_time=10:00&&end_time=11:00'
'&start_days=2&end_days=4&display=place'
)
call_params = mock_call_planitech.call_args[0][2]
assert call_params['startingDate'] == datetime(2018, 11, 11, 10, 0)
assert call_params['endingDate'] == datetime(2018, 11, 13, 0, 0)