diff --git a/conftest.py b/conftest.py index b37c41aca8e15e92668f784a51ca1cd0dfc3fa8a..36af21a0102f2de92172137517b845cfa872959f 100644 --- a/conftest.py +++ b/conftest.py @@ -3,88 +3,9 @@ conftest ~~~~~~~~ -This module defines the main configuration for the tests. +Empty conftest.py so that pytest finds the checkout pakage. :copyright: (c) 2017 European Spallation Source ERIC :license: BSD 2-Clause, see LICENSE for more details. """ -import pytest -from flask_ldap3_login import AuthenticationResponse, AuthenticationResponseStatus -from app.factory import create_app -from app.extensions import db -from app.defaults import defaults - - -@pytest.fixture(scope='session') -def app(request): - config = { - 'TESTING': True, - 'WTF_CSRF_ENABLED': False, - 'SQLALCHEMY_DATABASE_URI': 'postgresql://ics:icstest@postgres_test/inventory_db_test', - 'INVENTORY_LDAP_GROUPS': { - 'admin': 'Inventory Admin', - 'create': 'Inventory User', - } - } - app = create_app(config=config) - with app.app_context(): - db.drop_all() - db.engine.execute('CREATE EXTENSION IF NOT EXISTS citext') - db.create_all() - for instance in defaults: - db.session.add(instance) - db.session.flush() - db.session.expunge_all() - db.session.commit() - yield app - db.session.remove() - db.drop_all() - - -@pytest.fixture -def client(request, app): - return app.test_client() - - -# TODO: make this work to clean the database between tests -# @pytest.fixture(autouse=True) -# def dbsession(request, monkeypatch): -# # Roll back at the end of every test -# request.addfinalizer(db.session.remove) -# # Prevent the session from closing (make it a no-op) and -# # committing (redirect to flush() instead) -# monkeypatch.setattr(db.session, 'commit', db.session.flush) -# monkeypatch.setattr(db.session, 'remove', lambda: None) - - -@pytest.fixture(autouse=True) -def no_ldap_connection(monkeypatch): - """Make sure we don't make any connection to the LDAP server""" - monkeypatch.delattr('flask_ldap3_login.LDAP3LoginManager._make_connection') - - -@pytest.fixture(autouse=True) -def patch_ldap_authenticate(monkeypatch): - - def authenticate(self, username, password): - response = AuthenticationResponse() - response.user_id = username - response.user_dn = f'cn={username},dc=esss,dc=lu,dc=se' - if username == 'admin' and password == 'adminpasswd': - response.status = AuthenticationResponseStatus.success - response.user_info = {'cn': 'Admin User', 'mail': 'admin@example.com'} - response.user_groups = [{'cn': 'Inventory Admin'}] - elif username == 'user_rw' and password == 'userrw': - response.status = AuthenticationResponseStatus.success - response.user_info = {'cn': 'User RW', 'mail': 'user_rw@example.com'} - response.user_groups = [{'cn': 'Inventory User'}] - elif username == 'user_ro' and password == 'userro': - response.status = AuthenticationResponseStatus.success - response.user_info = {'cn': 'User RO', 'mail': 'user_ro@example.com'} - response.user_groups = [{'cn': 'ESS Employees'}] - else: - response.status = AuthenticationResponseStatus.fail - return response - - monkeypatch.setattr('flask_ldap3_login.LDAP3LoginManager.authenticate', authenticate) diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..2147fdbff7a47300f6559b5e00b9162594c1fc7e --- /dev/null +++ b/tests/functional/conftest.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" +tests.functional.conftest +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pytest fixtures common to all functional tests. + +:copyright: (c) 2017 European Spallation Source ERIC +:license: BSD 2-Clause, see LICENSE for more details. + +""" +import pytest +import sqlalchemy as sa +from flask_ldap3_login import AuthenticationResponse, AuthenticationResponseStatus +from app.factory import create_app +from app.extensions import db as _db + + +@pytest.fixture(scope='session') +def app(request): + """Session-wide test `Flask` application.""" + config = { + 'TESTING': True, + 'WTF_CSRF_ENABLED': False, + 'SQLALCHEMY_DATABASE_URI': 'postgresql://ics:icstest@postgres_test/inventory_db_test', + 'INVENTORY_LDAP_GROUPS': { + 'admin': 'Inventory Admin', + 'create': 'Inventory User', + } + } + app = create_app(config=config) + ctx = app.app_context() + ctx.push() + + def teardown(): + ctx.pop() + + request.addfinalizer(teardown) + return app + + +@pytest.fixture +def client(request, app): + return app.test_client() + + +@pytest.fixture(scope='session') +def db(app, request): + """Session-wide test database.""" + def teardown(): + _db.session.remove() + _db.drop_all() + + _db.app = app + _db.engine.execute('CREATE EXTENSION IF NOT EXISTS citext') + _db.create_all() + + request.addfinalizer(teardown) + return _db + + +@pytest.fixture(autouse=True) +def session(db, request): + """Creates a new database session for every test. + + Rollback any transaction to always leave the database clean + """ + connection = db.engine.connect() + transaction = connection.begin() + options = dict(bind=connection, binds={}) + session = db.create_scoped_session(options=options) + session.begin_nested() + + # session is actually a scoped_session + # for the `after_transaction_end` event, we need a session instance to + # listen for, hence the `session()` call + @sa.event.listens_for(session(), 'after_transaction_end') + def resetart_savepoint(sess, trans): + if trans.nested and not trans._parent.nested: + session.expire_all() + session.begin_nested() + + db.session = session + + yield session + + session.remove() + transaction.rollback() + connection.close() + + +@pytest.fixture(autouse=True) +def no_ldap_connection(monkeypatch): + """Make sure we don't make any connection to the LDAP server""" + monkeypatch.delattr('flask_ldap3_login.LDAP3LoginManager._make_connection') + + +@pytest.fixture(autouse=True) +def patch_ldap_authenticate(monkeypatch): + + def authenticate(self, username, password): + response = AuthenticationResponse() + response.user_id = username + response.user_dn = f'cn={username},dc=esss,dc=lu,dc=se' + if username == 'admin' and password == 'adminpasswd': + response.status = AuthenticationResponseStatus.success + response.user_info = {'cn': 'Admin User', 'mail': 'admin@example.com'} + response.user_groups = [{'cn': 'Inventory Admin'}] + elif username == 'user_rw' and password == 'userrw': + response.status = AuthenticationResponseStatus.success + response.user_info = {'cn': 'User RW', 'mail': 'user_rw@example.com'} + response.user_groups = [{'cn': 'Inventory User'}] + elif username == 'user_ro' and password == 'userro': + response.status = AuthenticationResponseStatus.success + response.user_info = {'cn': 'User RO', 'mail': 'user_ro@example.com'} + response.user_groups = [{'cn': 'ESS Employees'}] + else: + response.status = AuthenticationResponseStatus.fail + return response + + monkeypatch.setattr('flask_ldap3_login.LDAP3LoginManager.authenticate', authenticate) diff --git a/tests/test_api.py b/tests/functional/test_api.py similarity index 61% rename from tests/test_api.py rename to tests/functional/test_api.py index 530653886eb8ac84983471333471aecd1485efdf..db395dacc2c63a82d0effd1a15572c4b4adb21c7 100644 --- a/tests/test_api.py +++ b/tests/functional/test_api.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -tests.test_api -~~~~~~~~~~~~~~ +tests.functional.test_api +~~~~~~~~~~~~~~~~~~~~~~~~~ This module defines API tests. @@ -11,6 +11,16 @@ This module defines API tests. """ import json import pytest +from app import models + + +ENDPOINT_MODEL = { + 'manufacturers': models.Manufacturer, + 'models': models.Model, + 'locations': models.Location, + 'status': models.Status, +} +ENDPOINTS = list(ENDPOINT_MODEL.keys()) def get(client, url, token=None): @@ -94,36 +104,52 @@ def test_login(client): assert 'access_token' in response.json -def test_get_locations(client, readonly_token): - response = client.get('/api/locations') +@pytest.mark.parametrize('endpoint', ENDPOINTS) +def test_get_generic_model(endpoint, session, client, readonly_token): + model = ENDPOINT_MODEL[endpoint] + names = ('Foo', 'Bar', 'Alice') + for name in names: + session.add(model(name=name)) + session.commit() + response = client.get(f'/api/{endpoint}') check_response_message(response, 'Missing Authorization Header', 401) - response = get(client, '/api/locations', 'xxxxxxxxx') + response = get(client, f'/api/{endpoint}', 'xxxxxxxxx') check_response_message(response, 'Not enough segments', 422) - response = get(client, '/api/locations', readonly_token) - check_names(response, ('ICS lab', 'Utgård', 'Site', 'ESS')) + response = get(client, f'/api/{endpoint}', readonly_token) + check_names(response, names) -def test_create_locations_fail(client, readonly_token): - response = client.post('/api/locations') +@pytest.mark.parametrize('endpoint', ENDPOINTS) +def test_create_generic_model_fail(endpoint, client, readonly_token): + response = client.post(f'/api/{endpoint}') check_response_message(response, 'Missing Authorization Header', 401) - response = post(client, '/api/locations', data={}, token='xxxxxxxxx') + response = post(client, f'/api/{endpoint}', data={}, token='xxxxxxxxx') check_response_message(response, 'Not enough segments', 422) - response = post(client, '/api/locations', data={}, token=readonly_token) + response = post(client, f'/api/{endpoint}', data={}, token=readonly_token) check_response_message(response, "User doesn't have the required group", 403) + model = ENDPOINT_MODEL[endpoint] + assert model.query.count() == 0 -def test_create_locations(client, user_token): - response = post(client, '/api/locations', data={}, token=user_token) +@pytest.mark.parametrize('endpoint', ENDPOINTS) +def test_create_generic_model(endpoint, client, user_token): + response = post(client, f'/api/{endpoint}', data={}, token=user_token) check_response_message(response, "Missing mandatory field 'name'", 422) data = {'name': 'Foo'} - response = post(client, '/api/locations', data=data, token=user_token) + response = post(client, f'/api/{endpoint}', data=data, token=user_token) assert response.status_code == 201 - assert response.json == {'id': 5, 'name': 'Foo'} - response = post(client, '/api/locations', data=data, token=user_token) + assert {'id', 'name'} <= set(response.json.keys()) + assert response.json['name'] == 'Foo' + response = post(client, f'/api/{endpoint}', data=data, token=user_token) check_response_message(response, 'IntegrityError', 409) - response = post(client, '/api/locations', data={'name': 'foo'}, token=user_token) + response = post(client, f'/api/{endpoint}', data={'name': 'foo'}, token=user_token) check_response_message(response, 'IntegrityError', 409) - response = post(client, '/api/locations', data={'name': 'FOO'}, token=user_token) + response = post(client, f'/api/{endpoint}', data={'name': 'FOO'}, token=user_token) check_response_message(response, 'IntegrityError', 409) - response = get(client, '/api/locations', user_token) - check_names(response, ('ICS lab', 'Utgård', 'Site', 'ESS', 'Foo')) + data = {'name': 'Bar'} + response = post(client, f'/api/{endpoint}', data=data, token=user_token) + assert response.status_code == 201 + model = ENDPOINT_MODEL[endpoint] + assert model.query.count() == 2 + response = get(client, f'/api/{endpoint}', user_token) + check_names(response, ('Foo', 'Bar')) diff --git a/tests/test_basic.py b/tests/functional/test_web.py similarity index 95% rename from tests/test_basic.py rename to tests/functional/test_web.py index ee594471b7af260bab170f6831d96e9a34d0992b..d8e8527737982a3086b4942ee231b7cb1ed067f9 100644 --- a/tests/test_basic.py +++ b/tests/functional/test_web.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -tests.test_basic -~~~~~~~~~~~~~~~~ +tests.functional.test_web +~~~~~~~~~~~~~~~~~~~~~~~~~ This module defines basic web tests.