# passerelle - uniform access to multiple data sources and services # Copyright (C) 2021 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import base64 import json import os import urllib.parse import httmock import pytest from passerelle.base.models import AccessRight from passerelle.contrib.toulouse_foederis.models import Document, Resource from .utils import make_resource, mock_url def get_json_content(name): with open(f'tests/data/toulouse_foederis/{name}.json') as fd: return json.load(fd) HTTP_MOCKS = { 'type-emploi': { 'path': r'^/.*/data/type_emploi$', 'query': 'viewIntegrationName=api_publik', 'content': get_json_content('type_emploi'), }, 'categorie': { 'path': r'^/.*/data/categorie1$', 'query': 'viewIntegrationName=api_publik', 'content': get_json_content('categorie1'), }, 'filiere': { 'path': r'^.*/data/Filiere$', 'query': 'viewIntegrationName=api_publik', 'content': get_json_content('Filiere'), }, 'annonce': { 'path': r'^.*/data/annonce$', 'query': 'viewIntegrationName=api_publik', 'content': get_json_content('annonce'), }, 'pdf': { 'path': r'^.*/data/annonce/[0-9]+/fields/pdf_ddr$', 'query': 'viewIntegrationName=api_publik', 'content': get_json_content('pdf'), }, } APIKEY = '111c11ee-e1b1-11f1-11ce-11d11af1a111-1111-111111' @pytest.fixture def http_mock(): for annonce_r14848258 in [3450229, 3782122, 3782130, 4005534, 4005526]: HTTP_MOCKS['html-fields-%s' % annonce_r14848258] = { 'path': r'^.*/data/demande_de_personnel$', 'query': '&'.join( [ 'filterName=id', 'filterValue=%s' % annonce_r14848258, 'fieldList=missions' + urllib.parse.quote(',') + 'profil_requis', 'viewIntegrationName=api_publik', ] ), # give same html field contents for all annonces (only test first one here) 'content': get_json_content('demande_de_personnel'), } handlers = [] for defn in HTTP_MOCKS.values(): def make_handler(path, content, query=None): @httmock.urlmatch(path=path, query=query) def handler(url, request): assert request.headers['api-key'] == APIKEY return json.dumps(content) return handler handlers.append(make_handler(defn['path'], defn['content'], query=defn['query'])) @httmock.urlmatch() def error_handler(url, request): assert False, 'should not be reached' with httmock.HTTMock(*handlers, error_handler) as mock: yield mock @pytest.fixture def resource(db, settings, caplog): return make_resource( Resource, title='Foederis', slug='foederis', description='Foederis', url='https://passerelle.cutm-publik-preprod.nfrance.com/foederis/', api_key=APIKEY, ) def test_document_delete(resource, http_mock): resource.hourly() document = Document.objects.filter(external_id__startswith='announce-').first() pdf_path = document.pdf.path assert os.path.exists(pdf_path) document.delete() assert not os.path.exists(pdf_path) class TestHourly: def test_hourly(self, resource, http_mock, caplog): resource.hourly() assert 'Created announce 4229013' in caplog.text assert 'Referentials updated.' in caplog.text assert resource.last_update_announces assert resource.last_update_referentiels caplog.clear() resource.hourly() assert 'Created' not in caplog.text assert 'Updated' not in caplog.text assert 'Referentials updated.' in caplog.text caplog.clear() annonce = get_json_content('annonce') annonce['results'][0]['intitule_annonce'] = 'COIN' with mock_url(url=r'.*annonce$', response=json.dumps(annonce)): resource.hourly() assert 'Created' not in caplog.text assert 'Updated announce 3450231' in caplog.text assert 'Referentials updated.' in caplog.text # remove announce deleted from foederis assert Document.objects.filter(external_id__startswith='announce-').count() == 5 annonce = get_json_content('annonce') del annonce['results'][4] with mock_url(url=r'.*annonce$', response=json.dumps(annonce)): resource.hourly() assert Document.objects.filter(external_id__startswith='announce-').count() == 4 def test_loaded_pdf(self, resource, http_mock): resource.hourly() assert Document.objects.filter(external_id__startswith='announce-', pdf='').count() == 0 document = Document.objects.get(external_id='announce-4229013') with document.pdf.open(mode='rb') as fd: assert fd.read() == base64.b64decode( HTTP_MOCKS['pdf']['content']['results'][0]['pdf_ddr']['fileData'] ) def test_error_500(self, resource, caplog): with mock_url(status_code=500): resource.hourly() assert 'Service is unavailable' in caplog.text def test_not_json(self, resource, caplog): with mock_url(status_code=200): resource.hourly() assert 'Service is unavailable' in caplog.text def test_json_code_is_not_200(self, resource, caplog): with mock_url(status_code=200, response='{"code": "x"}'): resource.hourly() assert 'Service is unavailable' in caplog.text def test_data_annonce_error_500(self, resource, http_mock, caplog): with mock_url(url=r'/.*annonce.*', status_code=500): resource.hourly() assert 'Service is unavailable' in caplog.text def test_pdf_error_500(self, resource, http_mock, caplog): with mock_url(url=r'/.*pdf_ddr.*', status_code=500): resource.hourly() assert 'Service is unavailable' in caplog.text def test_html_fields_error_500(self, resource, http_mock, caplog): with mock_url(url=r'/.*demande_de_personnel.*', status_code=500): resource.hourly() assert 'Service is unavailable' in caplog.text class TestEndpoints: @pytest.fixture(autouse=True) def resource(self, resource, http_mock): resource.hourly() return resource @pytest.fixture(autouse=True) def freezer(self, freezer): freezer.move_to('2022-04-20T12:00:00') return freezer def test_data_sources(self, app): for name in ['type-emploi', 'categorie', 'filiere']: response = app.get(f'/toulouse-foederis/foederis/ds/{name}/') assert response.json['err'] == 0 assert response.json['last_update'] assert {d['id'] for d in response.json['data']} == { d['name'] for d in HTTP_MOCKS[name]['content']['results'] } def test_announce(self, app): response = app.get('/toulouse-foederis/foederis/announce/') content = response.json assert content['err'] == 0 assert len(content['data_sources']) == 3 data = content['data'] assert len(data) == 5 def setof(name): return {x[name] for x in data} assert setof('id') == {'3782242', '4005540', '3866743', '4229013', '3450231'} assert setof('text') == { 'AGENT OU AGENTE D ENTRETIEN EN CRECHE', 'Agent-e de collecte', 'ELAGUEUR', 'JARDINIER OU JARDINIERE', 'TEST PUBLIK', } assert setof('categorie') == {'', 'C-AGENTS EXECUTION', 'A-DIRECTION/CONCEPTION/ENCADT'} assert setof('type_emploi') == {'Permanent', 'Temporaire'} assert setof('filiere') == {'', 'FILIERE TECHNIQUE'} assert setof('date') == {'2022-04-11', '2022-04-06', '2022-03-17', '2022-04-19', '2022-04-13'} assert setof('date_fin_publication') == {'2022-05-11', '2022-04-25', '2022-12-31', '2022-04-30'} # check html fields assert '
Pilotage' in data[0]['description'] assert '
Connaissances' in data[0]['profil'] # check anti-chronological order on 'date' assert list(sorted(setof('date'), reverse=True)) == list(x['date'] for x in data) def test_announce_before_the_date(self, app, freezer): freezer.move_to('2022-04-13') response = app.get('/toulouse-foederis/foederis/announce/') assert len(response.json['data']) == 4 def test_announce_after_the_date(self, app, freezer): freezer.move_to('2022-05-01') response = app.get('/toulouse-foederis/foederis/announce/') assert len(response.json['data']) == 3 def test_announce_query_id(self, app): response = app.get('/toulouse-foederis/foederis/announce/?id=4005540') assert len(response.json['data']) == 1 assert response.json['data'][0]['id'] == '4005540' def test_announce_query_q(self, app): response = app.get('/toulouse-foederis/foederis/announce/?q=agent') def setof(name): return {x[name] for x in response.json['data']} assert len(response.json['data']) == 2 assert setof('text') == {'AGENT OU AGENTE D ENTRETIEN EN CRECHE', 'Agent-e de collecte'} def test_announce_query_type_emploi(self, app): response = app.get('/toulouse-foederis/foederis/announce/?type_emploi=Permanent') def setof(name): return {x[name] for x in response.json['data']} assert len(response.json['data']) == 4 assert setof('text') == { 'AGENT OU AGENTE D ENTRETIEN EN CRECHE', 'ELAGUEUR', 'JARDINIER OU JARDINIERE', 'TEST PUBLIK', } def test_announce_query_categorie(self, app): response = app.get('/toulouse-foederis/foederis/announce/?categorie=C-AGENTS%20EXECUTION') def setof(name): return {x[name] for x in response.json['data']} assert len(response.json['data']) == 3 assert setof('text') == { 'AGENT OU AGENTE D ENTRETIEN EN CRECHE', 'Agent-e de collecte', 'JARDINIER OU JARDINIERE', } def test_announce_query_filiere(self, app): response = app.get('/toulouse-foederis/foederis/announce/?filiere=FILIERE%20TECHNIQUE') def setof(name): return {x[name] for x in response.json['data']} assert len(response.json['data']) == 4 assert setof('text') == { 'AGENT OU AGENTE D ENTRETIEN EN CRECHE', 'Agent-e de collecte', 'JARDINIER OU JARDINIERE', 'TEST PUBLIK', } def test_announce_query_collectivite(self, app): response = app.get('/toulouse-foederis/foederis/announce/?collectivite=MAIRIE%20DE%20TOULOUSE') def setof(name): return {x[name] for x in response.json['data']} assert len(response.json['data']) == 3 assert setof('text') == { 'AGENT OU AGENTE D ENTRETIEN EN CRECHE', 'ELAGUEUR', 'JARDINIER OU JARDINIERE', } def test_announce_query_type_emploi_and_q(self, app): response = app.get('/toulouse-foederis/foederis/announce/?type_emploi=Permanent&q=agen') def setof(name): return {x[name] for x in response.json['data']} assert len(response.json['data']) == 1 assert setof('text') == { 'AGENT OU AGENTE D ENTRETIEN EN CRECHE', } def test_announce_pdf(self, resource, app): response = app.get('/toulouse-foederis/foederis/announce/') url = response.json['data'][0]['pdf_url'] assert url == 'http://testserver/toulouse-foederis/foederis/announce/4229013/pdf/' # verify access is public AccessRight.objects.all().delete() response = app.get(url) assert response.content.startswith(b'%PDF-1.4') app.get('/toulouse-foederis/foederis/announce/111/pdf/', status=404) assert response.headers['content-type'] == 'application/pdf'