Skip to content
Snippets Groups Projects
Commit 6f38b289 authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Add some tests

parent f3488f75
No related branches found
No related tags found
No related merge requests found
......@@ -4,3 +4,4 @@ __pycache__
*.pyc
settings*.cfg
/data
.cache
Makefile 0 → 100644
.PHONY: help db_test test
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " db_test to start the postgres database for test"
@echo " test to run the tests"
db_test:
docker-compose -f docker-compose-test.yml up -d postgres_test
test:
docker-compose -f docker-compose-test.yml run --rm web_test
......@@ -11,7 +11,7 @@ You can use docker for development:
1. Clone the repository
2. Create the database. Data will be stored under "./data" by default.
2. Create the database. Data will be stored under "./data/dev" by default.
You can export the PGDATA_VOLUME variable to use another directory::
# Start only postgres so it has time to initialize
......@@ -30,6 +30,23 @@ You can use docker for development:
Once the database has been created, you only need to run `docker-compose
up` to start the app.
Testing
-------
1. Create the database. Data will be stored under "./data/test"::
# Start only postgres so it has time to initialize
$ docker-compose -f docker-compose-test.yml up -d postgres_test
or
$ make db_test
2. Run the tests::
$ docker-compose -f docker-compose-test.yml run --rm web_test
or
$ make test
Backup & restore
----------------
......
# -*- coding: utf-8 -*-
"""
conftest
~~~~~~~~
This module defines the main configuration for the tests.
: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)
version: '2'
services:
web_test:
image: inventory
container_name: inventory_web_test
build: .
command: pytest -v
volumes:
- .:/app
depends_on:
- postgres_test
postgres_test:
image: postgres:9.6
container_name: inventory_postgres_test
expose:
- "5432"
volumes:
- ./data/test:/var/lib/postgresql/data/pgdata
environment:
POSTGRES_USER: ics
POSTGRES_PASSWORD: icstest
POSTGRES_DB: inventory_db_test
PGDATA: /var/lib/postgresql/data/pgdata
......@@ -8,7 +8,7 @@ dependencies:
- conda-forge::certifi=2017.4.17=py36_0
- conda-forge::click=6.7=py36_0
- conda-forge::colorama=0.3.9=py36_0
- conda-forge::flask=0.12=py36_0
- conda-forge::flask=0.12.2=py36_0
- conda-forge::flask-login=0.4.0=py36_0
- conda-forge::flask-sqlalchemy=2.2=py36_1
- conda-forge::flask-wtf=0.14.2=py36_0
......@@ -28,6 +28,8 @@ dependencies:
- conda-forge::pillow=4.2.1=py36_0
- conda-forge::pip=9.0.1=py36_0
- conda-forge::psycopg2=2.7.1=py36_0
- conda-forge::py=1.4.34=py36_0
- conda-forge::pytest=3.2.1=py36_0
- conda-forge::python=3.6.1=3
- conda-forge::python-dateutil=2.6.0=py36_0
- conda-forge::python-editor=1.0.3=py36_0
......
# -*- coding: utf-8 -*-
"""
tests.test_api
~~~~~~~~~~~~~~
This module defines API tests.
:copyright: (c) 2017 European Spallation Source ERIC
:license: BSD 2-Clause, see LICENSE for more details.
"""
import json
import pytest
def get(client, url, token=None):
response = client.get(
url,
headers={'Content-Type': 'application/json',
'Authorization': f'Bearer {token}'},
)
if response.headers['Content-Type'] == 'application/json':
response.json = json.loads(response.data)
return response
def post(client, url, data, token=None):
headers = {'Content-Type': 'application/json'}
if token is not None:
headers['Authorization'] = f'Bearer {token}'
response = client.post(url, data=json.dumps(data), headers=headers)
if response.headers['Content-Type'] == 'application/json':
response.json = json.loads(response.data)
return response
def login(client, username, password):
data = {
'username': username,
'password': password
}
return post(client, '/api/login', data)
def get_token(client, username, password):
response = login(client, username, password)
return response.json['access_token']
@pytest.fixture()
def readonly_token(client):
return get_token(client, 'user_ro', 'userro')
@pytest.fixture()
def user_token(client):
return get_token(client, 'user_rw', 'userrw')
@pytest.fixture()
def admin_token(client):
return get_token(client, 'admin', 'adminpasswd')
def check_response_message(response, msg, status_code=400):
assert response.status_code == status_code
try:
data = response.json
except AttributeError:
data = json.loads(response.data)
try:
message = data['message']
except KeyError:
# flask-jwt-extended is using "msg" instead of "message"
# in its default callbacks
message = data['msg']
assert message == msg
def check_names(response, names):
response_names = set(item['name'] for item in response.json)
assert set(names) == response_names
def test_login(client):
response = client.post('/api/login')
check_response_message(response, 'Body should be a JSON object')
response = post(client, '/api/login', data={'username': 'foo', 'passwd': ''})
check_response_message(response, 'Missing mandatory field (username or password)', 422)
response = login(client, 'foo', 'invalid')
check_response_message(response, 'Invalid credentials', 401)
response = login(client, 'user_ro', 'userro')
assert response.status_code == 200
assert 'access_token' in response.json
def test_get_locations(client, readonly_token):
response = client.get('/api/locations')
check_response_message(response, 'Missing Authorization Header', 401)
response = get(client, '/api/locations', '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'))
def test_create_locations_fail(client, readonly_token):
response = client.post('/api/locations')
check_response_message(response, 'Missing Authorization Header', 401)
response = post(client, '/api/locations', data={}, token='xxxxxxxxx')
check_response_message(response, 'Not enough segments', 422)
response = post(client, '/api/locations', data={}, token=readonly_token)
check_response_message(response, "User doesn't have the required group", 403)
def test_create_locations(client, user_token):
response = post(client, '/api/locations', 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)
assert response.status_code == 201
assert response.json == {'id': 5, 'name': 'Foo'}
response = post(client, '/api/locations', data=data, token=user_token)
check_response_message(response, 'IntegrityError', 409)
response = post(client, '/api/locations', data={'name': 'foo'}, token=user_token)
check_response_message(response, 'IntegrityError', 409)
response = post(client, '/api/locations', 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'))
# -*- coding: utf-8 -*-
"""
tests.test_basic
~~~~~~~~~~~~~~~~
This module defines basic web tests.
:copyright: (c) 2017 European Spallation Source ERIC
:license: BSD 2-Clause, see LICENSE for more details.
"""
def login(client, username, password):
data = {
'username': username,
'password': password
}
return client.post('/login', data=data, follow_redirects=True)
def logout(client):
return client.get('/logout', follow_redirects=True)
def test_login_logout(client):
response = login(client, 'unknown', 'invalid')
assert b'<title>Login</title>' in response.data
response = login(client, 'user_rw', 'invalid')
assert b'<title>Login</title>' in response.data
response = login(client, 'user_rw', 'userrw')
assert b'Welcome to the ICS Inventory!' in response.data
assert b'User RW' in response.data
response = logout(client)
assert b'<title>Login</title>' in response.data
def test_index(client):
response = client.get('/')
assert response.status_code == 302
assert '/login' in response.headers['Location']
login(client, 'user_ro', 'userro')
response = client.get('/')
assert b'Welcome to the ICS Inventory!' in response.data
assert b'User RO' in response.data
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment