passerelle/tests/test_address.py

300 lines
11 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
import json
from io import StringIO
from unittest import mock
import pytest
from django.core.files import File
import tests.utils
from passerelle.apps.base_adresse.models import BaseAdresse
from passerelle.apps.sector.models import SectorResource
from tests.test_manager import login
BAN = {
'attribution': 'BAN',
'features': [
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'housenumber': '169',
'id': '75114_1912_00169',
'importance': 0.77751,
'label': '169 Rue du Château 75014 Paris',
'name': '169 Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'housenumber': '167',
'id': '75114_1912_00167',
'importance': 0.77751,
'label': '167 Rue du Château 75014 Paris',
'name': '167 Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'housenumber': '170',
'id': '75114_1912_00169',
'importance': 0.77751,
'label': '170 Rue du Château 75014 Paris',
'name': '170 Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
# no house number
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'id': '75114_1912_00169',
'importance': 0.77751,
'label': 'XX Rue du Château 75014 Paris',
'name': 'Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'id': '75114_1913_00000',
'importance': 0.77751,
'label': 'YY Rue du Château 75014 Paris',
'name': 'Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
# empty id (no street_id)
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'housenumber': '169',
'id': '', # empty id => no street_id
'importance': 0.77751,
'label': '169 Rue du Château 75014 Paris',
'name': '169 Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
# id is not a string => no street_id
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'housenumber': '169',
'id': 42, # integer id => no street_id
'importance': 0.77751,
'label': '169 Rue du Château 75014 Paris',
'name': '169 Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
# id without "_" => no street_id
{
'geometry': {'coordinates': [2.323365, 48.833702], 'type': 'Point'},
'properties': {
'city': 'Paris',
'citycode': '75114',
'context': '75, Paris, Île-de-France',
'district': 'Paris 14e Arrondissement',
'housenumber': '169',
'id': '123', # no "_" => no street_id
'importance': 0.77751,
'label': '169 Rue du Château 75014 Paris',
'name': '169 Rue du Château',
'postcode': '75014',
'score': 0.9797736363636363,
'street': 'Rue du Château',
'type': 'housenumber',
'x': 650331.55,
'y': 6859506.98,
},
'type': 'Feature',
},
],
'licence': 'ETALAB-2.0',
'limit': 5,
'query': '169 rue du chateau, 75014 Paris',
'type': 'FeatureCollection',
'version': 'draft',
}
CSV = """street_id,street_name,parity,min_housenumber,max_housenumber,sector_id,sector_name
75114_1912,rue du Château,P,,,gs-moulin,Groupe Scolaire Moulin
75114_1912,rue du Château,I,0,167,gs-zola,Groupe Scolaire Zola
75114_1912,rue du Château,I,168,999999,ecole-hugo,École Hugo
75114_1913,rue 1913,,,,ecole-pascal,École Pascal
"""
@pytest.fixture
def sector(db):
return tests.utils.setup_access_rights(
SectorResource.objects.create(
slug='ecole',
title='Secteur scolaire',
description='desc',
csv_file=File(StringIO(CSV), 'sectorization.csv'),
)
)
@pytest.fixture
def base_adresse(db):
return tests.utils.setup_access_rights(BaseAdresse.objects.create(slug='base-adresse', zipcode='75114'))
@mock.patch('passerelle.utils.Request.get')
def test_sectorization(mocked_get, app, base_adresse, sector):
assert base_adresse.sectors.count() == 0
mocked_get.return_value = tests.utils.FakedResponse(content=json.dumps(BAN), status_code=200)
url = tests.utils.generic_endpoint_url('base-adresse', 'search', slug=base_adresse.slug)
addresses = app.get(url, params={'q': 'foo'}, status=200).json
assert len(addresses) == 8
for address in addresses:
assert 'sectors' not in address
assert 'sectorization_error' not in address
base_adresse.sectors.add(sector)
# empty sector
sector.sector_set.all().delete()
addresses = app.get(url, params={'q': 'foo'}, status=200).json
assert len(addresses) == 8
for address in addresses:
assert address['sectors'] == {} # sector is empty
sector.save() # reload CSV data in sector
addresses = app.get(url, params={'q': 'foo'}, status=200).json
assert addresses[0]['sectors'] == {'ecole': {'id': 'ecole-hugo', 'text': 'École Hugo'}}
assert addresses[1]['sectors'] == {'ecole': {'id': 'gs-zola', 'text': 'Groupe Scolaire Zola'}}
assert addresses[2]['sectors'] == {'ecole': {'id': 'gs-moulin', 'text': 'Groupe Scolaire Moulin'}}
# addresses without housenumber
assert addresses[3]['sectors'] == {}
assert addresses[4]['sectors'] == {'ecole': {'id': 'ecole-pascal', 'text': 'École Pascal'}}
# bad address format, no street_id
assert addresses[5]['sectors'] == {}
assert addresses[5]['sectorization_error'] == 'missing street_id in address'
assert addresses[6]['sectors'] == {}
assert addresses[6]['sectorization_error'] == 'missing street_id in address'
assert addresses[7]['sectors'] == {}
assert addresses[7]['sectorization_error'] == 'missing street_id in address'
url = tests.utils.generic_endpoint_url('base-adresse', 'sectors', slug=base_adresse.slug)
for params in (None, {'id': 'ecole'}, {'q': 'scolaire'}):
sectors = app.get(url, params=params, status=200).json
assert sectors['err'] == 0
assert sectors['data'] == [
{
'id': 'ecole',
'text': 'Secteur scolaire',
'sectors_url': 'http://localhost/sector/ecole/sectors/',
'description': 'desc',
}
]
for params in ({'id': 'foo'}, {'q': 'foo'}):
sectors = app.get(url, params=params, status=200).json
assert sectors['err'] == 0
assert sectors['data'] == []
def test_manager_with_or_without_sectors(db, app, admin_user, base_adresse, sector):
app = login(app)
path = '/manage/%s/add' % base_adresse.get_connector_slug()
resp = app.get(path)
assert 'Sectorizations' in str(resp.form.html)
assert 'Secteur scolaire [ecole]' in str(resp.form.html)
sector.delete()
resp = app.get(path)
assert 'Sectorizations' not in str(resp.form.html)
assert 'Secteur scolaire [ecole]' not in str(resp.form.html)