import json
from unittest import mock
import pytest
from django.core.management import call_command
import tests.utils
from passerelle.apps.opengis.models import FeatureCache, OpenGIS, Query
from passerelle.base.models import Job
from passerelle.utils import import_site
from tests.test_manager import login
pytestmark = pytest.mark.django_db
FAKE_FEATURE_INFO = '''
Parcelle cadastrale (Plan cadastral informatise du Grand Lyon)
4.784140,45.796890 4.784834,45.797365
69040BD309
040000BD0309
309
2406
Particulier
Parcelle figuree au plan
Arpentee
75404
bar
'''
FAKE_FEATURE_INFO_FEATURE_COLLECTION = '''
531238.20604672 5735107.61605308
546464.26335844 5751147.23178471
531238.20604672 5735107.61605308
546464.26335844 5751147.23178471
200046977-ZFE-001
2020-01-01
V5
24/7
V3
24/7
V3
24/7
V5
24/7
V5
24/7
https://agora.grandlyon.com/webdelib/files/unzip//seance_264250/23_d1647427974276.pdf
https://www.grandlyon.com/actions/zfe.html#c20726
...
200046977-ZFE-001
'''
FAKE_SERVICE_CAPABILITIES = '''
WFS2.0.0
application/gml+xml; version=3.2
application/json; subtype=geojson
'''
FAKE_SERVICE_CAPABILITIES_V1_0_0 = '''
WFS1.0.0
application/gml+xml; version=3.2
application/json; subtype=geojson
'''
FAKE_FEATURES_JSON = '''
{
"features": [
{
"geometry": null,
"id": "ref_metro_limites_communales.fid--204aa923_15ffdce8d91_-27be",
"properties": {
"nom": "Bri\u00e9-et-Angonnes"
},
"type": "Feature"
},
{
"geometry": null,
"id": "ref_metro_limites_communales.fid--204aa923_15ffdce8d91_-27bd",
"properties": {
"nom": "Champagnier"
},
"type": "Feature"
},
{
"geometry": null,
"id": "ref_metro_limites_communales.fid--204aa923_15ffdce8d91_-27bb",
"properties": {
"nom": "Claix"
},
"type": "Feature"
},
{
"geometry": null,
"id": "ref_metro_limites_communales.fid--204aa923_15ffdce8d91_-27ba",
"properties": {
"nom": "Corenc"
},
"type": "Feature"
},
{
"geometry": null,
"id": "ref_metro_limites_communales.fid--204aa923_15ffdce8d91_-27b9",
"properties": {
"nom": "\u00c9chirolles"
},
"type": "Feature"
},
{
"geometry": null,
"id": "ref_metro_limites_communales.fid--204aa923_15ffdce8d91_-27b8",
"properties": {
"nom": "Eybens"
},
"type": "Feature"
},
{
"geometry": null,
"id": "ref_metro_limites_communales.fid--204aa923_15ffdce8d91_-27b7",
"properties": {
"nom": "Fontaine"
},
"type": "Feature"
}
],
"type": "FeatureCollection"
}'''
FAKE_ERROR = '''
Could not parse CQL filter list.
Encountered "BIS" at line 1, column 129.
Was expecting one of:
<EOF>
"and" ...
"or" ...
";" ...
"/" ...
"*" ...
"+" ...
"-" ...
Parsing : strEqualsIgnoreCase(nom_commune, 'Grenoble') = true AND strEqualsIgnoreCase(nom_voie, 'rue albert recoura') = true AND numero=8 BIS.
'''
FAKE_GEOLOCATED_FEATURE = '''{
"crs": {
"properties": {
"name": "urn:ogc:def:crs:EPSG::3945"
},
"type": "name"
},
"features": [
{
"geometry": {
"coordinates": [
1914059.51,
4224699.2
],
"type": "Point"
},
"geometry_name": "the_geom",
"properties": {
"code_insee": 38185,
"code_post": 38000,
"nom_afnor": "BOULEVARD EDOUARD REY",
"nom_commune": "Grenoble",
"nom_voie": "boulevard \u00e9douard rey",
"numero": 17
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
1914042.47,
4224665.2
],
"type": "Point"
},
"geometry_name": "the_geom",
"properties": {
"code_insee": 38185,
"code_post": 38000,
"nom_commune": "Grenoble",
"nom_voie": "place victor hugo",
"numero": 2
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
1914035.7,
4224700.42
],
"type": "Point"
},
"geometry_name": "the_geom",
"properties": {
"code_insee": 38185,
"code_post": 38000,
"nom_commune": "Grenoble",
"nom_voie": "boulevard \u00e9douard rey",
"numero": 28
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
1914018.64,
4224644.61
],
"type": "Point"
},
"geometry_name": "the_geom",
"properties": {
"code_insee": 38185,
"code_post": 38000,
"nom_commune": "Grenoble",
"nom_voie": "place victor hugo",
"numero": 4
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
[
[1914018, 4224644],
[1914018, 4224844],
[1914318, 4224944],
[1914318, 4224544]
]
],
"type": "Polygon"
},
"geometry_name": "the_geom",
"properties": {
"code_insee": 38185,
"code_post": 38000,
"nom_commune": "Grenoble",
"nom_square": "place trapeze"
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
[
[1914059, 4224699],
[1914059, 4224899],
[1914259, 4224699]
]
],
"type": "Polygon"
},
"geometry_name": "the_geom",
"properties": {
"code_insee": 38185,
"code_post": 38000,
"nom_commune": "Grenoble",
"nom_square": "place triangle"
},
"type": "Feature"
}
],
"totalFeatures": 6,
"type": "FeatureCollection"
}'''
FAKE_GEOLOCATED_FEATURE_CIRCLE = {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'properties': {
'in-circle': False,
'in-bbox': False,
},
'geometry': {'type': 'Point', 'coordinates': [2.3555374145507812, 48.906705448392216]},
},
{
'type': 'Feature',
'properties': {
'in-circle': True,
'in-bbox': True,
},
'geometry': {'type': 'Point', 'coordinates': [2.3366546630859375, 48.86990906900767]},
},
{
'type': 'Feature',
'properties': {
'in-circle': True,
'in-bbox': True,
},
'geometry': {'type': 'Point', 'coordinates': [2.344207763671875, 48.82901755447848]},
},
{
'type': 'Feature',
'properties': {
'in-circle': False,
'in-bbox': True,
},
'geometry': {'type': 'Point', 'coordinates': [2.304, 48.8086]},
},
{
'type': 'Feature',
'properties': {
'in-circle': True,
'in-bbox': True,
},
'geometry': {
'type': 'Polygon',
'coordinates': [[[2.304, 48.8086], [2.304, 49.8086], [1.304, 48.8086]]],
},
},
{
'type': 'Feature',
'properties': {
'in-circle': False,
'in-bbox': False,
},
'geometry': {
'type': 'Polygon',
'coordinates': [[[-2.304, 48.8086], [-2.304, 49.8086], [-1.304, 48.8086]]],
},
},
],
}
@pytest.fixture
def connector():
return tests.utils.setup_access_rights(
OpenGIS.objects.create(
slug='test', wms_service_url='http://example.net/wms', wfs_service_url='http://example.net/wfs'
)
)
@pytest.fixture
def query(connector):
return Query.objects.create(
resource=connector,
name='Test Query',
slug='test_query',
description='Test query.',
typename='pvo_patrimoine_voirie.pvoparking',
filter_expression=(
'typeparking'
''
),
)
def geoserver_responses(url, **kwargs):
if kwargs['params'].get('request') == 'GetCapabilities':
assert kwargs['params'].get('service')
return tests.utils.FakedResponse(status_code=200, content=FAKE_SERVICE_CAPABILITIES)
return tests.utils.FakedResponse(status_code=200, content=FAKE_FEATURES_JSON)
def geoserver_responses_v1_0_0(url, **kwargs):
if kwargs['params'].get('request') == 'GetCapabilities':
assert kwargs['params'].get('service')
return tests.utils.FakedResponse(status_code=200, content=FAKE_SERVICE_CAPABILITIES_V1_0_0)
return tests.utils.FakedResponse(status_code=200, content=FAKE_FEATURES_JSON)
def geoserver_responses_errors(url, **kwargs):
if kwargs['params'].get('request') == 'GetCapabilities':
return tests.utils.FakedResponse(status_code=200, content=FAKE_SERVICE_CAPABILITIES)
return tests.utils.FakedResponse(status_code=200, content=FAKE_ERROR)
def geoserver_responses_errors_unparsable(url, **kwargs):
if kwargs['params'].get('request') == 'GetCapabilities':
return tests.utils.FakedResponse(status_code=200, content=FAKE_SERVICE_CAPABILITIES)
return tests.utils.FakedResponse(status_code=200, content=FAKE_ERROR[:10])
def geoserver_geolocated_responses(url, **kwargs):
if kwargs['params'].get('request') == 'GetCapabilities':
return tests.utils.FakedResponse(status_code=200, content=FAKE_SERVICE_CAPABILITIES)
return tests.utils.FakedResponse(status_code=200, content=FAKE_GEOLOCATED_FEATURE)
def geoserver_circle_responses(url, **kwargs):
if kwargs['params'].get('request') == 'GetCapabilities':
return tests.utils.FakedResponse(status_code=200, content=FAKE_SERVICE_CAPABILITIES)
return tests.utils.FakedResponse(status_code=200, content=json.dumps(FAKE_GEOLOCATED_FEATURE_CIRCLE))
@mock.patch('passerelle.utils.Request.get')
def test_feature_info(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'feature_info', slug=connector.slug)
assert endpoint == '/opengis/test/feature_info'
mocked_get.return_value = tests.utils.FakedResponse(content=FAKE_FEATURE_INFO, status_code=200)
resp = app.get(endpoint, params={'lat': '45.796890', 'lon': '4.784140'})
assert (
mocked_get.call_args[1]['params']['bbox']
== '532556.896735,5747844.261214,532579.160633,5747876.194333'
)
assert mocked_get.call_args[1]['params']['crs'] == 'EPSG:3857'
assert (
resp.json['data']['cad_cadastrecadparcelle_layer']['cad_cadastrecadparcelle_feature'][
'natureproprietaire'
]
== 'Particulier'
)
assert 'name' not in resp.json['data']['cad_cadastrecadparcelle_layer']
assert (
'boundedBy'
not in resp.json['data']['cad_cadastrecadparcelle_layer']['cad_cadastrecadparcelle_feature']
)
assert 'foo' in resp.json['data']['cad_cadastrecadparcelle_layer']['cad_cadastrecadparcelle_feature']
connector.projection = 'EPSG:4326'
connector.save()
resp = app.get(endpoint, params={'lat': '45.796890', 'lon': '4.784140'})
assert mocked_get.call_args[1]['params']['bbox'] == '45.796790,4.784040,45.796990,4.784240'
assert mocked_get.call_args[1]['params']['crs'] == 'EPSG:4326'
@mock.patch('passerelle.utils.Request.get')
def test_feature_info_feature_collection(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'feature_info', slug=connector.slug)
assert endpoint == '/opengis/test/feature_info'
mocked_get.return_value = tests.utils.FakedResponse(
content=FAKE_FEATURE_INFO_FEATURE_COLLECTION, status_code=200
)
resp = app.get(endpoint, params={'lat': '45.796890', 'lon': '4.784140'})
assert (
mocked_get.call_args[1]['params']['bbox']
== '532556.896735,5747844.261214,532579.160633,5747876.194333'
)
assert mocked_get.call_args[1]['params']['crs'] == 'EPSG:3857'
assert resp.json['data']['autobus_autocars_critair'] == 'V5'
@mock.patch('passerelle.utils.Request.get')
@pytest.mark.parametrize(
'lat,lon',
[
('bad-value', '4.784140'),
('45.796890', 'bad-value'),
],
)
def test_feature_info_bad_request(mocked_get, app, connector, lat, lon):
endpoint = tests.utils.generic_endpoint_url('opengis', 'feature_info', slug=connector.slug)
assert endpoint == '/opengis/test/feature_info'
mocked_get.return_value = tests.utils.FakedResponse(content=FAKE_FEATURE_INFO, status_code=200)
resp = app.get(endpoint, params={'lat': lat, 'lon': lon})
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'Bad coordinates format'
@mock.patch('passerelle.utils.Request.get')
def test_tile(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'tile', slug=connector.slug)
assert endpoint == '/opengis/test/tile'
mocked_get.return_value = tests.utils.FakedResponse(
content=b'\x89PNG\r\n\x1a\n\x00\x00...', status_code=200
)
resp = app.get(endpoint + '/16/33650/23378.png')
assert mocked_get.call_args[1]['params']['crs'] == 'EPSG:3857'
assert (
mocked_get.call_args[1]['params']['bbox']
== '539339.671580,5741338.068556,539951.167806,5741949.564782'
)
connector.projection = 'EPSG:4326'
connector.save()
resp = app.get(endpoint + '/16/33650/23378.png')
assert mocked_get.call_args[1]['params']['crs'] == 'EPSG:4326'
assert mocked_get.call_args[1]['params']['bbox'] == '45.756026,4.844971,45.759859,4.850464'
assert resp.content == b'\x89PNG\r\n\x1a\n\x00\x00...'
@mock.patch('passerelle.utils.Request.get')
def test_get_feature_with_no_wfs_url(mocked_get, app, connector):
connector.wfs_service_url = ''
connector.save()
endpoint = tests.utils.generic_endpoint_url('opengis', 'features', slug=connector.slug)
assert endpoint == '/opengis/test/features'
mocked_get.side_effect = geoserver_responses
resp = app.get(endpoint, params={'type_names': 'ref_metro_limites_communales', 'property_name': 'nom'})
assert resp.json['data'] is None
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'no wfs URL declared'
@mock.patch('passerelle.utils.Request.get')
def test_get_feature(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'features', slug=connector.slug)
assert endpoint == '/opengis/test/features'
mocked_get.side_effect = geoserver_responses
resp = app.get(endpoint, params={'type_names': 'ref_metro_limites_communales', 'property_name': 'nom'})
assert mocked_get.call_args[1]['params']['request'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['propertyName'] == 'nom'
assert mocked_get.call_args[1]['params']['typenames'] == 'ref_metro_limites_communales'
assert 'json' in mocked_get.call_args[1]['params']['outputFormat']
assert mocked_get.call_args[1]['params']['service'] == 'WFS'
assert mocked_get.call_args[1]['params']['version'] == connector.get_wfs_service_version()
assert len(resp.json['data']) == 7
for item in resp.json['data']:
assert 'text' in item
@mock.patch('passerelle.utils.Request.get')
def test_get_filtered_feature(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'features', slug=connector.slug)
mocked_get.side_effect = geoserver_responses
app.get(
endpoint,
params={
'type_names': 'ref_metro_limites_communales',
'property_name': 'nom',
'cql_filter': 'nom=\'Fontaine\'',
},
)
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\''
@mock.patch('passerelle.utils.Request.get')
def test_get_filtered_by_property_feature(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'features', slug=connector.slug)
mocked_get.side_effect = geoserver_responses
params = {
'type_names': 'ref_metro_limites_communales',
'property_name': 'nom',
'cql_filter': 'nom=\'Fontaine\'',
'filter_property_name': 'nom',
}
app.get(endpoint, params=params)
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\''
params['q'] = 'bens'
app.get(endpoint, params=params)
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\' AND nom LIKE \'%bens%\''
params['case-insensitive'] = True
app.get(endpoint, params=params)
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\' AND nom ILIKE \'%bens%\''
del params['case-insensitive']
params['case_insensitive'] = True
assert mocked_get.call_args[1]['params']['cql_filter'] == 'nom=\'Fontaine\' AND nom ILIKE \'%bens%\''
params.pop('cql_filter')
app.get(endpoint, params=params)
assert 'cql_filter' not in mocked_get.call_args[1]['params']
@mock.patch('passerelle.utils.Request.get')
def test_get_feature_error(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'features', slug=connector.slug)
assert endpoint == '/opengis/test/features'
mocked_get.side_effect = geoserver_responses_errors
resp = app.get(endpoint, params={'type_names': 'ref_metro_limites_communales', 'property_name': 'nom'})
assert mocked_get.call_args[1]['params']['request'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['propertyName'] == 'nom'
assert mocked_get.call_args[1]['params']['typenames'] == 'ref_metro_limites_communales'
assert 'json' in mocked_get.call_args[1]['params']['outputFormat']
assert mocked_get.call_args[1]['params']['service'] == 'WFS'
assert mocked_get.call_args[1]['params']['version'] == connector.get_wfs_service_version()
result = resp.json
assert result['err'] == 1
assert result['err_desc'] == 'OpenGIS Error: NoApplicableCode'
assert 'Could not parse' in result['data']['text']
@mock.patch('passerelle.utils.Request.get')
def test_get_feature_error2(mocked_get, app, connector):
endpoint = tests.utils.generic_endpoint_url('opengis', 'features', slug=connector.slug)
assert endpoint == '/opengis/test/features'
mocked_get.side_effect = geoserver_responses_errors_unparsable
resp = app.get(endpoint, params={'type_names': 'ref_metro_limites_communales', 'property_name': 'nom'})
assert mocked_get.call_args[1]['params']['request'] == 'GetFeature'
assert mocked_get.call_args[1]['params']['propertyName'] == 'nom'
assert mocked_get.call_args[1]['params']['typenames'] == 'ref_metro_limites_communales'
assert 'json' in mocked_get.call_args[1]['params']['outputFormat']
assert mocked_get.call_args[1]['params']['service'] == 'WFS'
assert mocked_get.call_args[1]['params']['version'] == connector.get_wfs_service_version()
result = resp.json
assert result['err'] == 1
assert result['err_desc'] == 'OpenGIS Error: unparsable error'
assert '