import json import os from unittest import mock import pytest from django.test import override_settings from django.utils.dateparse import parse_date import tests.utils from passerelle.apps.maelis.models import Link, Maelis from passerelle.apps.maelis.utils import ( decompose_event, get_school_year, month_range, week_boundaries_datetimes, ) pytestmark = pytest.mark.django_db TEST_BASE_DIR = os.path.join(os.path.dirname(__file__), 'data', 'maelis') def get_xml_file(filename): with open(os.path.join(TEST_BASE_DIR, filename), 'rb') as desc: return desc.read() def get_json_file(filename): with open(os.path.join(TEST_BASE_DIR, "%s.json" % filename)) as fd: return json.load(fd) @pytest.fixture def family_service_wsdl(): return get_xml_file('FamilyService.wsdl') @pytest.fixture def invoice_service_wsdl(): return get_xml_file('InvoiceService.wsdl') @pytest.fixture def activity_service_wsdl(): return get_xml_file('ActivityService.wsdl') @pytest.fixture def catalog_mocked_get(activity_service_wsdl, family_service_wsdl): return ( tests.utils.FakedResponse( content=family_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'} ), tests.utils.FakedResponse( content=activity_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'} ), ) @pytest.fixture def catalog_mocked_post(): return ( tests.utils.FakedResponse( content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'} ), tests.utils.FakedResponse( content=get_xml_file('readActivityListResponse.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ), ) @pytest.fixture def connector(db): return tests.utils.setup_access_rights( Maelis.objects.create( slug='test', login='foo', password='bar', base_url='http://www3.sigec.fr/entrouvertws/services/' ) ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_link_unlink(mocked_post, mocked_get, family_service_wsdl, connector, app): mocked_get.return_value = mock.Mock(content=family_service_wsdl) mocked_post.return_value = mock.Mock( content=get_xml_file('readFamilyByPasswordError.xml'), status_code=500, headers={'Content-Type': 'text/xml'}, ) assert Link.objects.count() == 0 resp = app.post_json( '/maelis/test/link?NameID=local', params={'family_id': '3264', 'password': 'wrong', 'school_year': '2020'}, ) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'E204 : Le mot de passe est incorrect' assert not resp.json['data'] assert Link.objects.count() == 0 resp = app.post_json('/maelis/test/unlink?NameID=local') assert resp.json['err'] assert resp.json['err_desc'] == 'User not linked to family' mocked_post.return_value = mock.Mock( content=get_xml_file('readFamilyByPasswordResult.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ) resp = app.post_json('/maelis/test/link?NameID=local', params={'family_id': '3264', 'password': '123456'}) assert resp.json['err'] == 0 assert resp.json['data'] assert resp.json['data']['number'] == 3264 assert Link.objects.count() == 1 link = Link.objects.get() assert link.name_id == 'local' assert link.family_id == '3264' resp = app.post_json('/maelis/test/unlink?NameID=local') assert resp.json['err'] == 0 assert Link.objects.count() == 0 @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_family_info(mocked_post, mocked_get, family_service_wsdl, connector, app): mocked_get.return_value = mock.Mock(content=family_service_wsdl) mocked_post.return_value = mock.Mock( content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'} ) assert Link.objects.count() == 0 Link.objects.create(resource=connector, family_id='3264', name_id='local') resp = app.get('/maelis/test/family-info?NameID=local') assert resp.json['data']["number"] == 3264 assert resp.json['data']['rl1InfoBean'] assert resp.json['data']['childInfoList'] resp = app.get('/maelis/test/children-info?NameID=local') assert resp.json['data'] for child in resp.json['data']: assert child['id'] assert child['text'] resp = app.get('/maelis/test/child-info?NameID=local&childID=21293') assert resp.json['data'] assert resp.json['data']['num'] == '21293' assert resp.json['data']['id'] == '21293' resp = app.get('/maelis/test/adults-info?NameID=local') assert resp.json['data'] for child in resp.json['data']: assert child['id'] assert child['text'] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_activity_list(mocked_post, mocked_get, catalog_mocked_get, catalog_mocked_post, connector, app): mocked_get.side_effect = catalog_mocked_get mocked_post.side_effect = catalog_mocked_post Link.objects.create(resource=connector, family_id='3264', name_id='local') resp = app.get('/maelis/test/activity-list?NameID=local&personID=21293') assert resp.json['data'] for activity in resp.json['data']: assert activity['id'] assert activity['text'] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_update_coordinates(mocked_post, mocked_get, family_service_wsdl, connector, app): mocked_get.return_value = mock.Mock(content=family_service_wsdl) mocked_post.side_effect = ( tests.utils.FakedResponse( content=get_xml_file('updateCoordinatesResponse.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ), tests.utils.FakedResponse( content=get_xml_file('updateCoordinatesError.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ), ) Link.objects.create(resource=connector, family_id='3264', name_id='local') resp = app.post_json( '/maelis/test/update-coordinates?NameID=local&personID=21293', params={'mail': 'foo@example.com'} ) assert resp.content is not None resp = app.post_json( '/maelis/test/update-coordinates?NameID=local&personID=21293', params={'town': 'Paris', 'num': '169'} ) assert resp.json['err'] assert resp.json['err_desc'] == 'E16 : Le code postal est obligatoire' @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_list_invoices(mocked_post, mocked_get, invoice_service_wsdl, connector, app): mocked_get.return_value = mock.Mock(content=invoice_service_wsdl) mocked_post.return_value = mock.Mock( content=get_xml_file('readInvoicesResponse.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ) Link.objects.create(resource=connector, family_id='3264', name_id='local') resp = app.get('/maelis/test/regie/1/invoices?NameID=local') assert resp.json['data'] for invoice in resp.json['data']: assert invoice['display_id'] assert invoice['label'] assert invoice['total_amount'] assert not invoice['paid'] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_get_invoice_details(mocked_post, mocked_get, invoice_service_wsdl, connector, app): mocked_get.return_value = mock.Mock(content=invoice_service_wsdl) mocked_post.return_value = mock.Mock( content=get_xml_file('readInvoicesResponse.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ) Link.objects.create(resource=connector, family_id='3264', name_id='local') resp = app.get('/maelis/test/regie/1/invoice/3264-53186?NameID=local') assert resp.json['data'] assert resp.json['data']['label'] == 'FACTURATION SEPTEMBRE 2014' assert resp.json['data']['display_id'] == '53186' @pytest.mark.parametrize( 'date, schoolyear', [ ('2020-07-30', 2019), ('2020-07-31', 2020), ], ) def test_get_school_year(date, schoolyear): date = parse_date(date) assert schoolyear == get_school_year(date) @pytest.mark.parametrize( 'date, monday, sunday', [ ('2020-12-27', '2020-12-21', '2020-12-27'), ('2020-12-28', '2020-12-28', '2021-01-03'), ('2020-12-31', '2020-12-28', '2021-01-03'), ('2021-01-01', '2020-12-28', '2021-01-03'), ('2021-01-03', '2020-12-28', '2021-01-03'), ], ) def test_week_boundaries(date, monday, sunday): start, end = week_boundaries_datetimes(date) assert start.strftime('%Y-%m-%d') == monday assert end.strftime('%Y-%m-%d') == sunday @pytest.mark.parametrize( 'start, end, items', [ ('2020-10-22', '2020-12-06', ['2020-10-01', '2020-11-01', '2020-12-01']), ('2020-12-31', '2021-01-03', ['2020-12-01', '2021-01-01']), ('2020-12-28', '2020-12-28', ['2020-12-01']), ('2021-01-03', '2020-12-01', []), ], ) def test_month_range(start, end, items): start = parse_date(start) end = parse_date(end) assert [x.strftime('%Y-%m-%d') for x in month_range(start, end)] == items def test_decompose_event(): resp = get_json_file('child_planning_before_decomposition') data = resp['data'] assert len(data) == 43 assert data[22]['text'] == 'JOURNEE' assert data[23]['text'] == 'MATIN' assert data[24]['text'] == 'MATIN ET REPAS' assert data[25]['text'] == 'APRES MIDI' assert not [x for x in data if x['text'] == 'REPAS'] # unit is break down into its components assert [x['text'] for x in decompose_event(data[22])] == ['Matinée', 'Repas', 'Après-midi'] assert [x['text'] for x in decompose_event(data[23])] == ['Matinée'] assert [x['text'] for x in decompose_event(data[24])] == ['Matinée', 'Repas'] assert [x['text'] for x in decompose_event(data[25])] == ['Après-midi'] # child_planning function use a dict to remove dupplicated components data = {x['id']: x for e in data for x in decompose_event(e)}.values() assert len(data) == 42 assert len([x for x in data if x['text'] == 'Repas']) == 1 assert len([x for x in data if 'EO0001' in x['slot_id']]) == 3 @override_settings(TIME_ZONE='Europe/Paris') @pytest.mark.parametrize( 'legacy, nb_events, nb_booked, response', [ ('please', 43, 9, 'child_planning_before_decomposition'), ('', 42, 9, 'child_planning_after_decomposition'), ], ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_get_child_planning( mocked_post, mocked_get, legacy, nb_events, nb_booked, response, family_service_wsdl, activity_service_wsdl, app, connector, ): mocked_get.side_effect = ( tests.utils.FakedResponse( content=family_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'} ), tests.utils.FakedResponse( content=activity_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'} ), tests.utils.FakedResponse( content=activity_service_wsdl, status_code=200, headers={'Content-Type': 'text/xml'} ), ) mocked_post.side_effect = ( tests.utils.FakedResponse( content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'} ), tests.utils.FakedResponse( content=get_xml_file('child_planning_readActivityListResponse.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ), tests.utils.FakedResponse( content=get_xml_file('child_planning_readChildMonthPlanningResponse.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ), ) Link.objects.create(resource=connector, family_id='3264', name_id='local') url = '/maelis/test/child-planning?NameID=local&childID=21293&start_date=2020-12-19' url += '&legacy=' + legacy resp = app.get(url) data = resp.json['data'] previous_event = {} for event in data: assert event['category'] assert event['text'] if previous_event: assert previous_event['id'] <= event['id'] previous_event = event assert len(data) == nb_events assert len([s for s in data if s['user_booking_status'] == 'booked']) == nb_booked assert resp.json == get_json_file(response) @pytest.mark.parametrize( 'parameters, nb_subscribed, nb_not_subscribed', [ ('&subscribingStatus=', 2, 18), ('&subscribingStatus=subscribed', 2, 0), ('&subscribingStatus=not-subscribed', 0, 17), ('&subscribePublication=ELN', 2, 48), ], ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_child_activities( mocked_post, mocked_get, parameters, nb_subscribed, nb_not_subscribed, catalog_mocked_get, catalog_mocked_post, connector, app, ): mocked_get.side_effect = catalog_mocked_get mocked_post.side_effect = catalog_mocked_post Link.objects.create(resource=connector, family_id='3264', name_id='local') url = '/maelis/test/child-activities?NameID=local&childID=21293&queryDate=2020-12-15' url += parameters resp = app.get(url) if parameters == '&subscribingStatus=': assert resp.json == get_json_file('child_activities') status = [x['user_subscribing_status'] for x in resp.json['data']] assert len([x for x in status if x == 'subscribed']) == nb_subscribed assert len([x for x in status if x == 'not-subscribed']) == nb_not_subscribed @pytest.mark.parametrize( 'parameters, err_desc', [ ('&childID=99999', 'Child not found'), ('&subscribingStatus=not-a-status', 'wrong value for subscribingStatus'), ("&queryDate=2020-02-31", 'not a valid date'), ("&queryDate=not-a-date", 'YYYY-MM-DD expected'), ], ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_child_activities_errors( mocked_post, mocked_get, parameters, err_desc, catalog_mocked_get, catalog_mocked_post, connector, app ): mocked_get.side_effect = catalog_mocked_get mocked_post.side_effect = catalog_mocked_post Link.objects.create(resource=connector, family_id='3264', name_id='local') url = '/maelis/test/child-activities?NameID=local&childID=21293' url += parameters resp = app.get(url) assert resp.json['err'] assert err_desc in resp.json['err_desc'] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_subscribe(mocked_post, mocked_get, family_service_wsdl, connector, app): mocked_get.return_value = mock.Mock(content=family_service_wsdl) mocked_post.side_effect = ( tests.utils.FakedResponse( content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'} ), tests.utils.FakedResponse( content=get_xml_file('subscribeActivityResult.xml'), status_code=200, headers={'Content-Type': 'text/xml'}, ), ) Link.objects.create(resource=connector, family_id='3264', name_id='local') resp = app.get( '/maelis/test/subscribe/?NameID=local&childID=21293' + '&activityID=A10003123507&unitID=A10003123507&placeID=A10000000211' + '&weeklyPlanning=XX1XX11&start_date=2020-08-01&end_date=2021-07-31' ) assert not resp.json['err'] assert resp.json['data']['state'] == {"idState": "1", "isWaitState": False, "libelle": "Confirmé"} @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_unsubscribe(mocked_post, mocked_get, family_service_wsdl, connector, app): mocked_get.return_value = mock.Mock(content=family_service_wsdl) mocked_post.side_effect = ( tests.utils.FakedResponse( content=get_xml_file('readFamily.xml'), status_code=200, headers={'Content-Type': 'text/xml'} ), tests.utils.FakedResponse( content=get_xml_file('deletesubscribe.xml'), status_code=200, headers={'Content-Type': 'text/xml'} ), ) Link.objects.create(resource=connector, family_id='3264', name_id='local') resp = app.get( '/maelis/test/unsubscribe/?NameID=local&childID=21293' + '&activityID=A10003121692&start_date=2020-08-01' ) assert not resp.json['err'] assert resp.json['data'] is None @pytest.mark.parametrize( 'parameters, nb_bus_lines', [ ('&direction=', 2), ('&direction=Aller', 1), ('&direction=retour', 1), ], ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_bus_lines( mocked_post, mocked_get, parameters, nb_bus_lines, catalog_mocked_get, catalog_mocked_post, connector, app ): mocked_get.side_effect = catalog_mocked_get mocked_post.side_effect = catalog_mocked_post Link.objects.create(resource=connector, family_id='3264', name_id='local') url = '/maelis/test/bus-lines?NameID=local&childID=21293' url += '&activityID=A10003131850&unitID=A10003131903&queryDate=2020-12-15' url += parameters resp = app.get(url) if parameters == '&direction=': assert resp.json == get_json_file('bus_lines') assert len(resp.json['data']) == nb_bus_lines @pytest.mark.parametrize( 'parameters, err_desc', [ ('&childID=99999', 'Child not found'), ('&queryDate=2020-02-31', 'not a valid date'), ('&queryDate=not-a-date', 'YYYY-MM-DD expected'), ('&direction=heaven', 'wrong value for direction'), ], ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_bus_lines_errors( mocked_post, mocked_get, parameters, err_desc, catalog_mocked_get, catalog_mocked_post, connector, app ): mocked_get.side_effect = catalog_mocked_get mocked_post.side_effect = catalog_mocked_post Link.objects.create(resource=connector, family_id='3264', name_id='local') url = '/maelis/test/bus-lines?NameID=local&childID=21293&activityID=1&unitID=2' url += parameters resp = app.get(url) assert resp.json['err'] assert err_desc in resp.json['err_desc'] @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_bus_stops(mocked_post, mocked_get, catalog_mocked_get, catalog_mocked_post, connector, app): mocked_get.side_effect = catalog_mocked_get mocked_post.side_effect = catalog_mocked_post Link.objects.create(resource=connector, family_id='3264', name_id='local') url = '/maelis/test/bus-stops?NameID=local&childID=21293' url += '&busActivityID=A10003151396&busUnitID=A10003151402&queryDate=2020-12-15' resp = app.get(url) assert resp.json == get_json_file('bus_stops') assert len(resp.json['data']) == 6 @pytest.mark.parametrize( 'parameters, err_desc', [ ('&childID=99999', 'Child not found'), ('&queryDate=2020-02-31', 'not a valid date'), ('&queryDate=not-a-date', 'YYYY-MM-DD expected'), ('&busActivityID=1', 'Bus activity not found: 1'), ('&busUnitID=2', 'Bus unit not found: 2'), ], ) @mock.patch('passerelle.utils.Request.get') @mock.patch('passerelle.utils.Request.post') def test_bus_stops_errors( mocked_post, mocked_get, parameters, err_desc, catalog_mocked_get, catalog_mocked_post, connector, app ): mocked_get.side_effect = catalog_mocked_get mocked_post.side_effect = catalog_mocked_post Link.objects.create(resource=connector, family_id='3264', name_id='local') url = '/maelis/test/bus-stops?NameID=local&childID=21293' url += '&busActivityID=A10003151396&busUnitID=A10003151402' url += parameters resp = app.get(url) assert resp.json['err'] assert err_desc in resp.json['err_desc']