347 lines
13 KiB
Python
347 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Passerelle - uniform access to data and services
|
|
# Copyright (C) 2015 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/>.
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
import os
|
|
import json
|
|
import random
|
|
import warnings
|
|
|
|
import mock
|
|
import pytest
|
|
|
|
import utils
|
|
|
|
from passerelle.apps.arcgis.models import ArcGIS
|
|
from passerelle.base.models import ResourceLog, ProxyLogger, BaseResource, HTTPResource
|
|
from passerelle.apps.mdel.models import MDEL
|
|
from passerelle.contrib.stub_invoices.models import StubInvoicesConnector
|
|
from passerelle.utils.api import endpoint
|
|
|
|
|
|
@pytest.fixture
|
|
def mdel(db):
|
|
return utils.setup_access_rights(MDEL.objects.create(slug='test'))
|
|
|
|
|
|
@pytest.fixture
|
|
def arcgis(db):
|
|
instance = utils.setup_access_rights(ArcGIS.objects.create(slug='test'))
|
|
instance.set_log_level('DEBUG')
|
|
return instance
|
|
|
|
|
|
DEMAND_STATUS = {
|
|
'closed': True,
|
|
'status': 'accepted',
|
|
'comment': 'dossier trait\xe9.'
|
|
}
|
|
|
|
|
|
@mock.patch('passerelle.apps.mdel.models.Demand.get_status', lambda x: DEMAND_STATUS)
|
|
@mock.patch('passerelle.apps.mdel.models.Demand.create_zip', lambda x, y: '1-14-ILE-LA')
|
|
def test_generic_payload_logging(caplog, app, mdel):
|
|
filename = os.path.join(os.path.dirname(__file__), 'data', 'mdel', 'formdata.json')
|
|
payload = json.load(open(filename))
|
|
resp = app.post_json('/mdel/test/create', params=payload, status=200)
|
|
assert resp.json['data']['demand_id'] == '1-14-ILE-LA'
|
|
|
|
resp = app.get('/mdel/test/status', params={'demand_id': '1-14-ILE-LA'})
|
|
data = resp.json['data']
|
|
|
|
assert data['closed'] is True
|
|
assert data['status'] == 'accepted'
|
|
assert data['comment'] == 'dossier trait\xe9.'
|
|
|
|
records = [record for record in caplog.records if record.name == 'passerelle.resource.mdel.test']
|
|
for record in records:
|
|
assert record.levelname == 'INFO'
|
|
assert record.connector == 'mdel'
|
|
if record.connector_endpoint_method == 'POST':
|
|
assert 'endpoint POST /mdel/test/create' in record.message
|
|
assert record.connector_endpoint == 'create'
|
|
else:
|
|
assert 'endpoint GET /mdel/test/status?demand_id=1-14-ILE-LA' in record.message
|
|
assert record.connector_endpoint == 'status'
|
|
assert record.connector_endpoint_url == '/mdel/test/status?demand_id=1-14-ILE-LA'
|
|
|
|
|
|
@mock.patch('passerelle.utils.Request.get')
|
|
def test_proxy_logger(mocked_get, caplog, app, arcgis):
|
|
payload = open(os.path.join(os.path.dirname(__file__), 'data', 'nancy_arcgis', 'sigresponse.json')).read()
|
|
mocked_get.return_value = utils.FakedResponse(content=payload, status_code=200)
|
|
|
|
# simple logger
|
|
arcgis.log_evel = 'DEBUG'
|
|
logger = ProxyLogger(connector=arcgis)
|
|
logger.debug('this is a debug test')
|
|
logger.info('this is an info test')
|
|
|
|
assert ResourceLog.objects.count() == 2
|
|
for log in ResourceLog.objects.all():
|
|
if log.levelno == 10:
|
|
assert log.message == 'this is a debug test'
|
|
else:
|
|
assert log.message == 'this is an info test'
|
|
|
|
log = ResourceLog.objects.filter(appname='arcgis', slug='test').delete()
|
|
caplog.clear()
|
|
resp = app.get('/arcgis/test/district', params={'lon': 6.172122, 'lat': 48.673836}, status=200)
|
|
|
|
# Resource Custom DB Logger
|
|
log = ResourceLog.objects.filter(appname='arcgis', slug='test').first()
|
|
assert log.appname == 'arcgis'
|
|
assert log.slug == 'test'
|
|
assert log.levelno == 20
|
|
assert log.sourceip == '127.0.0.1'
|
|
assert log.extra['connector'] == 'arcgis'
|
|
assert log.extra['connector_endpoint'] == 'district'
|
|
assert log.extra['connector_endpoint_method'] == 'GET'
|
|
assert '/arcgis/test/district?' in log.extra['connector_endpoint_url']
|
|
|
|
# Resource Generic Logger
|
|
for record in caplog.records:
|
|
if record.name != 'passerelle.resource.arcgis.test':
|
|
continue
|
|
assert record.levelno == 20
|
|
assert record.levelname == 'INFO'
|
|
assert record.name == 'passerelle.resource.arcgis.test'
|
|
assert u"endpoint GET /arcgis/test/district?" in record.message
|
|
|
|
data = resp.json['data']
|
|
assert data['id'] == 4
|
|
assert data['text'] == 'HAUSSONVILLE / BLANDAN / MON DESERT / SAURUPT'
|
|
|
|
# when changing log level
|
|
ResourceLog.objects.all().delete()
|
|
arcgis.set_log_level('INFO')
|
|
arcgis.save()
|
|
app.get('/arcgis/test/district', params={'lon': 6.172122, 'lat': 48.673836}, status=200)
|
|
assert ResourceLog.objects.count() == 1
|
|
|
|
arcgis.logger.warning('first warning')
|
|
assert ResourceLog.objects.count() == 2
|
|
assert ResourceLog.objects.last().message == 'first warning'
|
|
assert ResourceLog.objects.last().levelno == 30
|
|
|
|
|
|
class FakeConnectorBase(object):
|
|
slug = 'connector'
|
|
|
|
def get_connector_slug(self):
|
|
return 'fake'
|
|
|
|
@endpoint()
|
|
def foo1(self, request):
|
|
pass
|
|
|
|
@endpoint(name='bar')
|
|
def foo2(self, request, param1):
|
|
pass
|
|
|
|
@endpoint()
|
|
def foo3(self, request, param1, param2):
|
|
pass
|
|
|
|
@endpoint()
|
|
def foo4(self, request, param1, param2='a', param3='b'):
|
|
pass
|
|
|
|
@endpoint(pattern='^test/$', example_pattern='test/')
|
|
def foo5(self, request, param1='a', param2='b', param3='c'):
|
|
pass
|
|
|
|
@endpoint(pattern=r'^(?P<param1>\w+)/?$',
|
|
example_pattern='{param1}/',
|
|
parameters={
|
|
'param1': {
|
|
'description': 'param 1',
|
|
'example_value': 'bar'
|
|
}})
|
|
def foo6(self, request, param1, param2='a'):
|
|
pass
|
|
|
|
@endpoint(description_get='foo7 get', description_post='foo7 post',
|
|
methods=['get', 'post'])
|
|
def foo7(self, request, param1='a', param2='b', param3='c'):
|
|
pass
|
|
|
|
@endpoint(cache_duration=10)
|
|
def cached_endpoint(self, request):
|
|
pass
|
|
|
|
|
|
def test_endpoint_decorator():
|
|
connector = FakeConnectorBase()
|
|
for i in range(6):
|
|
getattr(connector, 'foo%d' % (i + 1)).endpoint_info.object = connector
|
|
|
|
assert connector.foo1.endpoint_info.name == 'foo1'
|
|
assert connector.foo2.endpoint_info.name == 'bar'
|
|
assert not connector.foo1.endpoint_info.has_params()
|
|
assert connector.foo2.endpoint_info.has_params()
|
|
assert connector.foo2.endpoint_info.get_params() == [{'name': 'param1'}]
|
|
assert connector.foo3.endpoint_info.get_params() == [{'name': 'param1'}, {'name': 'param2'}]
|
|
assert connector.foo4.endpoint_info.get_params() == [
|
|
{'name': 'param1'},
|
|
{'name': 'param2', 'optional': True, 'default_value': 'a'},
|
|
{'name': 'param3', 'optional': True, 'default_value': 'b'}]
|
|
assert connector.foo5.endpoint_info.get_params() == [
|
|
{'name': 'param1', 'optional': True, 'default_value': 'a'},
|
|
{'name': 'param2', 'optional': True, 'default_value': 'b'},
|
|
{'name': 'param3', 'optional': True, 'default_value': 'c'}]
|
|
|
|
assert connector.foo6.endpoint_info.get_params() == [
|
|
{'name': 'param1', 'description': 'param 1'},
|
|
{'name': 'param2', 'optional': True, 'default_value': 'a'}]
|
|
|
|
assert connector.foo1.endpoint_info.example_url() == '/fake/connector/foo1'
|
|
assert connector.foo1.endpoint_info.example_url_as_html() == '/fake/connector/foo1'
|
|
assert connector.foo2.endpoint_info.example_url() == '/fake/connector/bar'
|
|
assert connector.foo3.endpoint_info.example_url() == '/fake/connector/foo3'
|
|
assert connector.foo5.endpoint_info.example_url() == '/fake/connector/foo5/test/'
|
|
assert connector.foo5.endpoint_info.example_url_as_html() == '/fake/connector/foo5/test/'
|
|
assert connector.foo6.endpoint_info.example_url() == '/fake/connector/foo6/bar/'
|
|
assert connector.foo6.endpoint_info.example_url_as_html() == '/fake/connector/foo6/<i class="varname">param1</i>/'
|
|
|
|
connector.foo6.endpoint_info.pattern = None
|
|
connector.foo6.endpoint_info.example_pattern = None
|
|
assert connector.foo6.endpoint_info.example_url() == '/fake/connector/foo6?param1=bar'
|
|
assert (connector.foo6.endpoint_info.example_url_as_html()
|
|
== '/fake/connector/foo6?param1=<i class="varname">param1</i>')
|
|
|
|
connector.foo7.endpoint_info.http_method = 'get'
|
|
assert connector.foo7.endpoint_info.description == 'foo7 get'
|
|
connector.foo7.endpoint_info.http_method = 'post'
|
|
assert connector.foo7.endpoint_info.description == 'foo7 post'
|
|
assert connector.foo7.endpoint_info.cache_duration is None
|
|
|
|
assert connector.cached_endpoint.endpoint_info.cache_duration == 10
|
|
|
|
|
|
def test_endpoint_description_in_template(app, db):
|
|
StubInvoicesConnector(slug='fake').save()
|
|
resp = app.get('/stub-invoices/fake/')
|
|
assert 'Get invoice details' in resp.text
|
|
|
|
|
|
def test_endpoint_cache(app, db, monkeypatch):
|
|
|
|
@endpoint(cache_duration=10, methods=['get', 'post'], pattern=r'^(?P<url_param>\w+)/$')
|
|
def randominvoice(obj, request, url_param, get_param=None):
|
|
return {'data': random.randint(0, pow(10, 12))}
|
|
|
|
monkeypatch.setattr(StubInvoicesConnector, 'randominvoice', randominvoice, raising=False)
|
|
|
|
connector = StubInvoicesConnector(slug='fake')
|
|
connector.save()
|
|
|
|
class TestCache(object):
|
|
|
|
def __init__(self):
|
|
self.d = dict()
|
|
self.get_calls = 0
|
|
self.set_calls = 0
|
|
|
|
def get(self, key):
|
|
self.get_calls += 1
|
|
return self.d.get(key)
|
|
|
|
def set(self, key, value, timeout):
|
|
self.set_calls += 1
|
|
self.d[key] = value
|
|
|
|
def delete(self, key):
|
|
del self.d[key]
|
|
|
|
cache = TestCache()
|
|
import passerelle.views
|
|
monkeypatch.setattr(passerelle.views, 'cache', cache)
|
|
|
|
resp1 = app.get('/stub-invoices/fake/randominvoice/url_param_value/?get_param=get_param_value')
|
|
assert cache.get_calls == 1
|
|
assert cache.set_calls == 1
|
|
|
|
resp2 = app.get('/stub-invoices/fake/randominvoice/url_param_value/?get_param=get_param_value')
|
|
assert cache.get_calls == 2
|
|
assert cache.set_calls == 1
|
|
assert resp1.json_body == resp2.json_body
|
|
|
|
resp3 = app.get(
|
|
'/stub-invoices/fake/randominvoice/url_param_value/?get_param=get_param_value'
|
|
'&apikey=somekey×tamp=somestamp')
|
|
assert cache.get_calls == 3
|
|
assert cache.set_calls == 1
|
|
assert resp1.json_body == resp3.json_body
|
|
|
|
resp4 = app.get('/stub-invoices/fake/randominvoice/url_param_value/?get_param=other_value')
|
|
assert cache.get_calls == 4
|
|
assert cache.set_calls == 2
|
|
assert resp1.json_body != resp4.json_body
|
|
|
|
resp5 = app.get('/stub-invoices/fake/randominvoice/other_value/?get_param=get_param_value')
|
|
assert cache.get_calls == 5
|
|
assert cache.set_calls == 3
|
|
assert resp1.json_body != resp5.json_body
|
|
|
|
resp6 = app.post('/stub-invoices/fake/randominvoice/other_value/?get_param=get_param_value')
|
|
assert cache.get_calls == 5
|
|
assert cache.set_calls == 3
|
|
assert resp1.json_body != resp6.json_body
|
|
|
|
|
|
def test_endpoint_cookies(app, db, monkeypatch, httpbin):
|
|
|
|
@endpoint(methods=['get'])
|
|
def httpcall(obj, request):
|
|
response = obj.requests.get(httpbin.url + '/cookies/set?foo=bar', allow_redirects=False)
|
|
cookie1 = response.request.headers.get('Cookie')
|
|
response = obj.requests.get(httpbin.url + '/get')
|
|
cookie2 = response.request.headers.get('Cookie')
|
|
return {
|
|
'cookie1': cookie1,
|
|
'cookie2': cookie2
|
|
}
|
|
|
|
monkeypatch.setattr(StubInvoicesConnector, 'httpcall', httpcall, raising=False)
|
|
|
|
connector = StubInvoicesConnector(slug='fake')
|
|
connector.save()
|
|
|
|
json_res = app.get('/stub-invoices/fake/httpcall').json
|
|
assert json_res['cookie1'] is None
|
|
assert json_res['cookie2'] == 'foo=bar'
|
|
# Do it a second time to test that no cookies are leaking from one call
|
|
# to the other
|
|
json_res = app.get('/stub-invoices/fake/httpcall').json
|
|
assert json_res['cookie1'] is None
|
|
assert json_res['cookie2'] == 'foo=bar'
|
|
|
|
|
|
def test_https_warnings(app, db, monkeypatch, httpbin_secure, relax_openssl):
|
|
from requests.exceptions import SSLError
|
|
from passerelle.contrib.maarch.models import Maarch
|
|
|
|
resource = utils.make_resource(Maarch, wsdl_url='https://example.com/', slug='slug', verify_cert=True)
|
|
with pytest.raises(SSLError):
|
|
resource.requests.get(httpbin_secure.join('/get/'))
|
|
resource.verify_cert = False
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter('error')
|
|
resource.requests.get(httpbin_secure.join('/get/'))
|