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)