diff --git a/.travis.yml b/.travis.yml index f5f7406b3..c1f382a2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,18 @@ -language: python -sudo: false -python: -- '2.7' +#Travis OSX currently doesn't not support docker (via virtualbox). +# "This computer doesn't have VT-X/AMD-v enabled." +sudo: required +dist: trusty install: - - bin/install.sh --ci + - sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D + - sudo sh -c "echo 'deb https://apt.dockerproject.org/repo ubuntu-trusty main' > /etc/apt/sources.list.d/docker.list" + - sudo apt-get update -qq + - sudo apt-get -o Dpkg::Options::="--force-confnew" install -y -q docker-engine + - sudo curl -o /usr/local/bin/docker-compose -L https://github.com/docker/compose/releases/download/1.6.2/docker-compose-`uname -s`-`uname -m` + - sudo chmod +x /usr/local/bin/docker-compose +before_script: + - sudo bin/install.sh --ci script: - bin/runtests.sh unit --ci + - bin/runtests.sh integration --ci after_success: - coveralls diff --git a/bin/runtests.sh b/bin/runtests.sh index 6dc31dede..1d3e23c54 100755 --- a/bin/runtests.sh +++ b/bin/runtests.sh @@ -1,6 +1,8 @@ #!/bin/bash +set -e unit_test_path=test/unit_tests/ +integration_test_path=test/integration_tests/ code_path=api/ cd "$( dirname "${BASH_SOURCE[0]}" )/.." @@ -16,8 +18,17 @@ case "$1-$2" in unit---watch) PYTHONPATH=. ptw $unit_test_path $code_path --poll -- $unit_test_path ;; - integration-|integration---ci|integration---watch) - echo "Not implemented yet" + integration---ci|integration-) + docker-compose \ + -f test/docker-compose.yml \ + run \ + --rm \ + bootstrap && \ + docker-compose -f test/docker-compose.yml run --rm integration-test + docker-compose -f test/docker-compose.yml down + ;; + integration---watch) + echo "Not implemented" ;; *) echo "Usage: $0 unit|integration [--ci|--watch]" diff --git a/docker/Dockerfile b/docker/Dockerfile index bdd29e645..773805d17 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -88,4 +88,4 @@ RUN /inject_build_info.sh ${BRANCH_LABEL} ${COMMIT_HASH} \ ENTRYPOINT ["/var/scitran/uwsgi-entrypoint.sh"] -CMD ["uwsgi", "--ini", "/var/scitran/config/uwsgi-config.ini", "--http", "0.0.0.0:8080" ] +CMD ["uwsgi", "--ini", "/var/scitran/config/uwsgi-config.ini", "--http", "0.0.0.0:8080", "--http-keepalive", "--so-keepalive", "--add-header", "Connection: Keep-Alive" ] diff --git a/test/Dockerfile b/test/Dockerfile new file mode 100644 index 000000000..a58e8f8b6 --- /dev/null +++ b/test/Dockerfile @@ -0,0 +1,12 @@ +FROM python:2.7 + +ENV MONGO_PATH='mongodb://mongo:27017/scitran' +ENV BASE_URL='http://scitran-core:8080/api' + +VOLUME /usr/src/tests +WORKDIR /usr/src/tests + +COPY requirements-integration-test.txt requirements.txt +RUN pip install -r requirements.txt + +ENTRYPOINT ["py.test"] diff --git a/test/docker-compose.yml b/test/docker-compose.yml new file mode 100644 index 000000000..18e7f3238 --- /dev/null +++ b/test/docker-compose.yml @@ -0,0 +1,42 @@ +version: '2' +services: + scitran-base: + build: + context: .. + dockerfile: docker/Dockerfile + scitran-core: + extends: scitran-base + links: + - mongo + environment: + - SCITRAN_CORE_DRONE_SECRET=changeme + - SCITRAN_PERSISTENT_DB_URI=mongodb://mongo:27017/scitran + - SCITRAN_CORE_INSECURE=true + - SCITRAN_CORE_LOG_LEVEL=debug + - SCITRAN_SITE_API_URL=http://127.0.0.1:8080/api + ports: + - "127.0.0.1:8080:8080" + volumes: + - .:/var/scitran/test-config + - ..:/var/scitran/code/api/ + bootstrap: + extends: scitran-base + links: + - scitran-core + volumes: + - .:/var/scitran/test-config + - ..:/var/scitran/code/api + command: /var/scitran/code/api/bin/bootstrap.py --insecure --secret changeme http://scitran-core:8080/api /var/scitran/test-config/test_bootstrap.json + mongo: + image: mongo + integration-test: + build: + context: ../test + dockerfile: Dockerfile + environment: + - MONGO_PATH=mongodb://mongo:27017/scitran + links: + - scitran-core + - mongo + volumes: + - ./integration_tests:/usr/src/tests diff --git a/test/integration_tests/conftest.py b/test/integration_tests/conftest.py new file mode 100644 index 000000000..18ed318e6 --- /dev/null +++ b/test/integration_tests/conftest.py @@ -0,0 +1,157 @@ +import os +import time +import json +import pytest +import pymongo +import requests + + +@pytest.fixture(scope="session") +def bunch(): + class BunchFactory: + def create(): + return type('Bunch', (), {}) + create = staticmethod(create) + + return BunchFactory() + + +@pytest.fixture(scope="module") +def db(): + mongo_path = os.environ.get('MONGO_PATH', 'mongodb://localhost:9001/scitran') + return pymongo.MongoClient(mongo_path).get_default_database() + + +@pytest.fixture(scope="module") +def base_url(): + return os.environ.get('BASE_URL', 'http://localhost:8080/api') + + +class RequestsAccessor(object): + def __init__(self, base_url, default_params): + self.base_url = base_url + self.default_params = default_params + + def _get_params(self, **kwargs): + params = self.default_params.copy() + if ("params" in kwargs): + params.update(kwargs["params"]) + return params + + def post(self, url_path, *args, **kwargs): + kwargs['params'] = self._get_params(**kwargs) + return requests.post(self.base_url + url_path, verify=False, *args, **kwargs) + + def delete(self, url_path, *args, **kwargs): + kwargs['params'] = self._get_params(**kwargs) + return requests.delete(self.base_url + url_path, verify=False, *args, **kwargs) + + def get(self, url_path, *args, **kwargs): + kwargs['params'] = self._get_params(**kwargs) + return requests.get(self.base_url + url_path, verify=False, *args, **kwargs) + + def put(self, url_path, *args, **kwargs): + kwargs['params'] = self._get_params(**kwargs) + return requests.put(self.base_url + url_path, verify=False, *args, **kwargs) + + +@pytest.fixture(scope="module") +def api_as_admin(base_url): + accessor = RequestsAccessor(base_url, {"user": "admin@user.com", "root": "true"}) + return accessor + + +@pytest.fixture(scope="module") +def api_as_user(base_url): + accessor = RequestsAccessor(base_url, {"user": "admin@user.com"}) + return accessor + + +@pytest.fixture(scope="module") +def api_accessor(base_url): + class RequestsAccessorWithBaseUrl(RequestsAccessor): + def __init__(self, user): + super(self.__class__, self).__init__(base_url, {"user": user}) + + return RequestsAccessorWithBaseUrl + + +@pytest.fixture() +def with_a_group_and_a_project(api_as_admin, data_builder, request, bunch): + + user_1 = 'user1@user.com' + user_2 = 'user2@user.com' + + group_id = 'test_group_' + str(int(time.time() * 1000)) + data_builder.create_group(group_id) + project_id = data_builder.create_project(group_id) + + def teardown_db(): + data_builder.delete_project(project_id) + data_builder.delete_group(group_id) + + request.addfinalizer(teardown_db) + + fixture_data = bunch.create() + fixture_data.project_id = project_id + fixture_data.user_1 = user_1 + fixture_data.user_2 = user_2 + return fixture_data + + +@pytest.fixture(scope="module") +def data_builder(api_as_admin): + class DataBuilder: + + # This function is called whenever DataBuilder.create_X() or + # DataBuilder.delete_X() is called (those functions don't + # actually exist). It returns a callable object that does the right + # thing based on what X is. + # This madness allows significantly reduced copy/paste code and more + # readable tests. + def __getattr__(self, name): + parent_attribute = { + 'group': '_id', + 'project': 'group', + 'session': 'project', + 'acquisition': 'session' + } + + # create_( parent_id ) + # + # Call the functions create_group(), create_project(), create_session(), + # or create_acquisition(), passing in the "parent" container id as the singular + # parameter. + if name.startswith('create_'): + def create_(parent_id): + container = name.split('create_')[1] + parent = parent_attribute[container] + api_path = '/' + container + 's' # API paths are pluralized + + payload = {parent: parent_id} + if (container != 'group'): + payload.update({'public': True, 'label': container + '_testing'}) + payload = json.dumps(payload) + + print api_path, payload + r = api_as_admin.post(api_path, data=payload) + print r.content + + assert r.ok + + return json.loads(r.content)['_id'] + return create_ + + # delete_( id ) + # + # Call the functions delete_group(), delete_project(), delete_session(), + # or delete_acquisition(), passing in the id as the only parameter. + if name.startswith('delete_'): + def delete_(id): + container = name.split('delete_')[1] + api_path = '/' + container + 's' # API paths are pluralized + r = api_as_admin.delete(api_path + '/' + id) + assert r.ok + + return delete_ + return DataBuilder() diff --git a/test/integration_tests/test_collection.py b/test/integration_tests/test_collection.py index 8d34581d2..c5c83d901 100644 --- a/test/integration_tests/test_collection.py +++ b/test/integration_tests/test_collection.py @@ -1,106 +1,92 @@ -import requests import json import logging +import pytest log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -log.setLevel(logging.INFO) -import warnings -warnings.filterwarnings('ignore') +def test_collections(api_as_user, single_project_session_acquisition_tree): + data = single_project_session_acquisition_tree -from nose.tools import with_setup -import pymongo -from bson.objectid import ObjectId + my_collection_id = create_collection(api_as_user) + get_collection(api_as_user, my_collection_id) + add_session_to_collection(api_as_user, data.sid, my_collection_id) -db = pymongo.MongoClient('mongodb://localhost/scitran').get_default_database() + r = api_as_user.get('/acquisitions/' + data.aid) + collections = json.loads(r.content)['collections'] + assert my_collection_id in collections -base_url = 'https://localhost:8443/api' -test_data = type('',(object,),{})() + delete_collection(api_as_user, my_collection_id) -def setup_db(): - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - test_data.pid = json.loads(r.content)['_id'] - assert r.ok - log.debug('pid = \'{}\''.format(test_data.pid)) - - payload = { - 'project': test_data.pid, - 'label': 'session_testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/sessions?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - test_data.sid = json.loads(r.content)['_id'] - log.debug('sid = \'{}\''.format(test_data.sid)) - - payload = { - 'session': test_data.sid, - 'label': 'acq_testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/acquisitions?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - test_data.aid = json.loads(r.content)['_id'] - log.debug('aid = \'{}\''.format(test_data.aid)) + r = api_as_user.get('/collections/' + my_collection_id) + assert r.status_code == 404 -def teardown_db(): - r = requests.delete(base_url + '/acquisitions/' + test_data.aid + '?user=admin@user.com&root=true', verify=False) - assert r.ok - r = requests.delete(base_url + '/sessions/' + test_data.sid + '?user=admin@user.com&root=true', verify=False) - assert r.ok - r = requests.delete(base_url + '/projects/' + test_data.pid + '?user=admin@user.com&root=true', verify=False) - assert r.ok + r = api_as_user.get('/acquisitions/' + data.aid) + collections = json.loads(r.content)['collections'] + assert my_collection_id not in collections + + +# This fixture sets up a single project->session->acquisition hierarchy in the db +@pytest.fixture(scope="module") +def single_project_session_acquisition_tree(api_as_admin, request, bunch, data_builder): + + pid = data_builder.create_project('scitran') + sid = data_builder.create_session(pid) + aid = data_builder.create_acquisition(sid) + def teardown_db(): + data_builder.delete_acquisition(aid) + data_builder.delete_session(sid) + data_builder.delete_project(pid) -@with_setup(setup_db, teardown_db) -def test_collections(): - payload = { + # Setup teardown handler + request.addfinalizer(teardown_db) + + # This sets up a poor man's dot-notation dict + fixture_data = bunch.create() + fixture_data.sid = sid + fixture_data.aid = aid + return fixture_data + + +# Return collection id created +def create_collection(api): + # POST - Create a collection + NEW_COLLECTION_JSON = json.dumps({ 'curator': 'admin@user.com', 'label': 'SciTran/Testing', 'public': True - } - r = requests.post(base_url + '/collections?user=admin@user.com', data=json.dumps(payload), verify=False) + }) + r = api.post('/collections', data=NEW_COLLECTION_JSON) assert r.ok - _id = json.loads(r.content)['_id'] - log.debug('_id = \'{}\''.format(_id)) - r = requests.get(base_url + '/collections/' + _id + '?user=admin@user.com', verify=False) + return json.loads(r.content)['_id'] + + +def get_collection(api, collection_id): + # GET - Retrieve a collection + r = api.get('/collections/' + collection_id) assert r.ok - payload = { - 'contents':{ + + +def add_session_to_collection(api, session_id, collection_id): + # PUT - Add session to collection + ADD_SESSION_TO_COLLECTION_JSON = json.dumps({ + 'contents': { 'nodes': [{ 'level': 'session', - '_id': test_data.sid + '_id': session_id, }], 'operation': 'add' } - } - r = requests.put(base_url + '/collections/' + _id + '?user=admin@user.com', data=json.dumps(payload), verify=False) - assert r.ok - r = requests.get(base_url + '/collections/' + _id + '/acquisitions?session=' + test_data.sid + '&user=admin@user.com', verify=False) + }) + r = api.put('/collections/' + collection_id, data=ADD_SESSION_TO_COLLECTION_JSON) assert r.ok - coll_acq_id= json.loads(r.content)[0]['_id'] - assert coll_acq_id == test_data.aid - acq_ids = [ObjectId(test_data.aid)] - acs = db.acquisitions.find({'_id': {'$in': acq_ids}}) - for ac in acs: - assert len(ac['collections']) == 1 - assert ac['collections'][0] == ObjectId(_id) - r = requests.delete(base_url + '/collections/' + _id + '?user=admin@user.com', verify=False) + + +def delete_collection(api, collection_id): + # DELETE - Delete the collection + r = api.delete('/collections/' + collection_id) assert r.ok - r = requests.get(base_url + '/collections/' + _id + '?user=admin@user.com', verify=False) - assert r.status_code == 404 - acs = db.acquisitions.find({'_id': {'$in': acq_ids}}) - for ac in acs: - assert len(ac['collections']) == 0 diff --git a/test/integration_tests/test_containers.py b/test/integration_tests/test_containers.py index f6e2887df..652a90225 100644 --- a/test/integration_tests/test_containers.py +++ b/test/integration_tests/test_containers.py @@ -1,137 +1,87 @@ -import requests import json +import time import logging +import pytest + log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -requests.packages.urllib3.disable_warnings() -base_url = 'https://localhost:8443/api' +@pytest.fixture(scope="module") +def with_two_groups(api_as_admin, bunch, request, data_builder): + group_1 = data_builder.create_group('test_group_' + str(int(time.time() * 1000))) + group_2 = data_builder.create_group('test_group_' + str(int(time.time() * 1000))) -def test_projects(): - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - _id = json.loads(r.content)['_id'] - r = requests.get(base_url + '/projects/' + _id + '?user=admin@user.com&root=true', verify=False) - assert r.ok - payload = { - 'group': 'scitran', - } - payload = json.dumps(payload) - r = requests.put(base_url + '/projects/' + _id + '?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - r = requests.delete(base_url + '/projects/' + _id + '?user=admin@user.com&root=true', verify=False) - assert r.ok + def teardown_db(): + data_builder.delete_group(group_1) + data_builder.delete_group(group_2) -def test_sessions(): - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - pid = json.loads(r.content)['_id'] - payload = { - 'project': pid, - 'label': 'session_testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/sessions?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - _id = json.loads(r.content)['_id'] - r = requests.get(base_url + '/sessions/' + _id + '?user=admin@user.com&root=true', verify=False) - assert r.ok - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing 2', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - new_pid = json.loads(r.content)['_id'] - assert r.ok - payload = { - 'project': new_pid, - } - payload = json.dumps(payload) - r = requests.put(base_url + '/sessions/' + _id + '?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - r = requests.delete(base_url + '/sessions/' + _id + '?user=admin@user.com&root=true', verify=False) - assert r.ok - r = requests.get(base_url + '/sessions/' + _id + '?user=admin@user.com&root=true', verify=False) - assert r.status_code == 404 - r = requests.delete(base_url + '/projects/' + pid + '?user=admin@user.com&root=true', verify=False) - assert r.ok - r = requests.delete(base_url + '/projects/' + new_pid + '?user=admin@user.com&root=true', verify=False) - assert r.ok + request.addfinalizer(teardown_db) -def test_acquisitions(): - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - pid = json.loads(r.content)['_id'] - payload = { - 'project': pid, - 'label': 'session_testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/sessions?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - sid = json.loads(r.content)['_id'] - - payload = { - 'project': pid, - 'label': 'session_testing_1', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/sessions?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - new_sid = json.loads(r.content)['_id'] - - payload = { - 'session': sid, - 'label': 'acq_testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/acquisitions?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - aid = json.loads(r.content)['_id'] + fixture_data = bunch.create() + fixture_data.group_1 = group_1 + fixture_data.group_2 = group_2 + return fixture_data - r = requests.get(base_url + '/acquisitions/' + aid + '?user=admin@user.com&root=true', verify=False) - assert r.ok - payload = { - 'session': new_sid - } - payload = json.dumps(payload) - r = requests.put(base_url + '/acquisitions/' + aid + '?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok +# Create project +# Add it to a group +# Switch the project to the second group +# Delete the project +def test_switching_project_between_groups(with_two_groups, data_builder, api_as_user): + data = with_two_groups - r = requests.delete(base_url + '/acquisitions/' + aid + '?user=admin@user.com&root=true', verify=False) - assert r.ok - r = requests.get(base_url + '/acquisitions/' + aid + '?user=admin@user.com&root=true', verify=False) - assert r.status_code == 404 - r = requests.delete(base_url + '/sessions/' + sid + '?user=admin@user.com&root=true', verify=False) + pid = data_builder.create_project(data.group_1) + assert api_as_user.get('/projects/' + pid).ok + r = api_as_user.get('/groups/' + data.group_1 + '/projects') + print json.loads(r.content) + + payload = json.dumps({'group': with_two_groups.group_2}) + r = api_as_user.put('/projects/' + pid, data=payload) assert r.ok - r = requests.delete(base_url + '/sessions/' + new_sid + '?user=admin@user.com&root=true', verify=False) + + r = api_as_user.get('/projects/' + pid) + assert r.ok and json.loads(r.content)['group'] == data.group_2 + + data_builder.delete_project(pid) + + +def test_switching_session_between_projects(with_two_groups, data_builder, api_as_user): + data = with_two_groups + + project_1_id = data_builder.create_project(data.group_1) + project_2_id = data_builder.create_project(data.group_1) + session_id = data_builder.create_session(project_1_id) + + payload = json.dumps({'project': project_2_id}) + r = api_as_user.put('/sessions/' + session_id, data=payload) assert r.ok - r = requests.delete(base_url + '/projects/' + pid + '?user=admin@user.com&root=true', verify=False) + + r = api_as_user.get('/sessions/' + session_id) + assert r.ok and json.loads(r.content)['project'] == project_2_id + + data_builder.delete_session(session_id) + data_builder.delete_project(project_1_id) + data_builder.delete_project(project_2_id) + + +def test_switching_acquisition_between_projects(with_two_groups, data_builder, api_as_user): + data = with_two_groups + + project_id = data_builder.create_project(data.group_1) + session_1_id = data_builder.create_session(project_id) + session_2_id = data_builder.create_session(project_id) + acquisition_id = data_builder.create_acquisition(session_1_id) + + payload = json.dumps({'session': session_2_id}) + r = api_as_user.put('/acquisitions/' + acquisition_id, data=payload) assert r.ok + + r = api_as_user.get('/acquisitions/' + acquisition_id) + assert r.ok and json.loads(r.content)['session'] == session_2_id + + data_builder.delete_acquisition(acquisition_id) + data_builder.delete_session(session_1_id) + data_builder.delete_session(session_2_id) + data_builder.delete_project(project_id) diff --git a/test/integration_tests/test_download.py b/test/integration_tests/test_download.py new file mode 100644 index 000000000..0cecc0762 --- /dev/null +++ b/test/integration_tests/test_download.py @@ -0,0 +1,90 @@ +import os +import json +import time +import pytest +import logging +import tarfile +import cStringIO + +log = logging.getLogger(__name__) +sh = logging.StreamHandler() +log.addHandler(sh) + + +@pytest.fixture() +def with_a_download_available(api_as_admin, data_builder, bunch, request): + file_name = "test.csv" + group_id = 'test_group_' + str(int(time.time() * 1000)) + + data_builder.create_group(group_id) + project_id = data_builder.create_project(group_id) + session_id = data_builder.create_session(project_id) + acquisition_id = data_builder.create_acquisition(session_id) + + # multiform fields for the file upload + files = {'file': (file_name, 'some,data,to,send\nanother,row,to,send\n'), + 'tags': ('', '["incomplete"]'), + 'metadata': + ('', + '''{ "group": {"_id": "scitran"}, + "project": {"label": "Testdata"}, + "session": {"uid": "1.2.840.113619.6.353.10437158128305161617400354036593525848"}, + "file": {"type": "nifti"}, + "acquisition": {"uid": "1.2.840.113619.2.353.4120.7575399.14591.1403393566.658_1", + "timestamp": "1970-01-01T00:00:00", + "label": "Screen Save", + "instrument": "MRI", + "measurement": "screensave", + "timezone": "America/Los_Angeles"}, + "subject": {"code": "ex7236"} + }''' + ) + } + + # upload the same file to each container created in the test + api_as_admin.post('/acquisitions/' + acquisition_id + '/files', files=files) + api_as_admin.post('/sessions/' + session_id + '/files', files=files) + api_as_admin.post('/projects/' + project_id + '/files', files=files) + + def teardown_download(): + api_as_admin.delete('/acquisitions/' + acquisition_id) + api_as_admin.delete('/sessions/' + session_id) + api_as_admin.delete('/projects/' + project_id) + api_as_admin.delete('/groups/' + group_id) + + request.addfinalizer(teardown_download) + + fixture_data = bunch.create() + fixture_data.project_id = project_id + fixture_data.file_name = file_name + return fixture_data + + +def test_download(with_a_download_available, api_as_admin): + data = with_a_download_available + + # Retrieve a ticket for a batch download + payload = json.dumps({ + 'optional': False, + 'nodes': [ + { + 'level': 'project', + '_id': data.project_id + } + ] + }) + r = api_as_admin.post('/download', data=payload) + assert r.ok + + # Perform the download + ticket = json.loads(r.content)['ticket'] + r = api_as_admin.get('/download', params={'ticket': ticket}) + assert r.ok + + tar_file = cStringIO.StringIO(r.content) + tar = tarfile.open(mode="r", fileobj=tar_file) + + # Verify a single file in tar with correct file name + for tarinfo in tar: + assert os.path.basename(tarinfo.name) == data.file_name + tar.close() diff --git a/test/integration_tests/test_errors.py b/test/integration_tests/test_errors.py index 84a884618..df8dd3cce 100644 --- a/test/integration_tests/test_errors.py +++ b/test/integration_tests/test_errors.py @@ -1,26 +1,27 @@ -import requests import json +import time import logging + log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -import warnings -warnings.filterwarnings('ignore') -base_url = 'https://localhost:8443/api' -import pymongo -db = pymongo.MongoClient('mongodb://localhost/scitran').get_default_database() -projects = db.projects -def test_extra_param(): - payload = { +def test_extra_param(api_as_admin): + label = 'SciTran/testing_' + str(int(time.time() * 1000)) + + bad_payload = json.dumps({ 'group': 'unknown', - 'label': 'SciTran/Testing', + 'label': label, 'public': False, 'extra_param': 'some_value' - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) + }) + + r = api_as_admin.post('/projects', data=bad_payload) assert r.status_code == 400 - r = projects.delete_many({'label': 'SciTran/Testing'}) - assert r.deleted_count == 0 + + r = api_as_admin.get('/projects') + assert r.ok + projects = json.loads(r.content) + filtered_projects = filter(lambda e: e['label'] == label, projects) + assert len(filtered_projects) == 0 diff --git a/test/integration_tests/test_groups.py b/test/integration_tests/test_groups.py index 170f6ceb6..0db3b74f6 100644 --- a/test/integration_tests/test_groups.py +++ b/test/integration_tests/test_groups.py @@ -1,31 +1,29 @@ import requests import json +import time import logging + log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -requests.packages.urllib3.disable_warnings() -base_url = 'https://localhost:8443/api' +def test_groups(api_as_admin, data_builder): + group_id = 'test_group_' + str(int(time.time() * 1000)) -def test_groups(): - _id = 'test' - r = requests.get(base_url + '/groups/' + _id + '?user=admin@user.com&root=true', verify=False) + # Cannot find a non-existant group + r = api_as_admin.get('/groups/' + group_id) assert r.status_code == 404 - payload = { - '_id': _id - } - payload = json.dumps(payload) - r = requests.post(base_url + '/groups?user=admin@user.com&root=true', data=payload, verify=False) - assert r.ok - r = requests.get(base_url + '/groups/' + _id + '?user=admin@user.com&root=true', verify=False) - assert r.ok - payload = { - 'name': 'Test group', - } - payload = json.dumps(payload) - r = requests.put(base_url + '/groups/' + _id + '?user=admin@user.com&root=true', data=payload, verify=False) + + data_builder.create_group(group_id) + + # Able to find new group + r = api_as_admin.get('/groups/' + group_id) assert r.ok - r = requests.delete(base_url + '/groups/' + _id + '?user=admin@user.com&root=true', verify=False) + + # Able to change group name + payload = json.dumps({'name': 'Test group'}) + r = api_as_admin.put('/groups/' + group_id, data=payload) assert r.ok + + data_builder.delete_group(group_id) diff --git a/test/integration_tests/test_notes.py b/test/integration_tests/test_notes.py index 42188ba62..7a3e8acb0 100644 --- a/test/integration_tests/test_notes.py +++ b/test/integration_tests/test_notes.py @@ -1,77 +1,45 @@ -import requests import json -import warnings -from nose.tools import with_setup +import time +import pytest import logging log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -log.setLevel(logging.INFO) -warnings.filterwarnings('ignore') -adm_user = 'admin@user.com' -user = 'test@user.com' -test_data = type('',(object,),{})() -base_url = 'https://localhost:8443/api' -def _build_url(_id=None, requestor=adm_user): - if _id is None: - url = test_data.proj_url + '?user=' + requestor - else: - url = test_data.proj_url + '/' + _id + '?user=' + requestor - return url +def test_notes(with_a_group_and_a_project, api_as_user): + data = with_a_group_and_a_project + notes_path = '/projects/' + data.project_id + '/notes' - -def setup_db(): - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - test_data.pid = json.loads(r.content)['_id'] - assert r.ok - log.debug('pid = \'{}\''.format(test_data.pid)) - test_data.proj_url = base_url + '/projects/{}/notes'.format(test_data.pid) - data = { - '_id': user, - 'site': 'local', - 'access': 'rw' - } - url = base_url + '/projects/' + test_data.pid + '/permissions?user=admin@user.com&root=true' - r = requests.post(url, data=json.dumps(data), verify=False) + # Add a note + new_note = json.dumps({'text': 'test note'}) + r = api_as_user.post(notes_path, data=new_note) assert r.ok -def teardown_db(): - r = requests.delete(base_url + '/projects/' + test_data.pid + '?user=admin@user.com&root=true', verify=False) + # Verify note is present in project + r = api_as_user.get('/projects/' + data.project_id) assert r.ok + project = json.loads(r.content) + assert len(project['notes']) == 1 -@with_setup(setup_db, teardown_db) -def test_notes(): - url_post = _build_url(requestor=user) - data = {'user': user, 'text':'test note'} - r = requests.post(url_post, data=json.dumps(data), verify=False) - assert r.ok - r = requests.get(base_url + '/projects/{}?user={}'.format(test_data.pid, adm_user), verify=False) - assert r.ok - p = json.loads(r.content) - assert len(p['notes']) == 1 - assert p['notes'][0]['user'] == user - note_id = p['notes'][0]['_id'] - url_get = _build_url(note_id, user) - r = requests.get(url_get, verify=False) - assert r.ok - assert json.loads(r.content)['_id'] == note_id - data = {'text':'modified test note'} - r = requests.put(url_get, data=json.dumps(data), verify=False) - assert r.ok - r = requests.get(url_get, verify=False) + note_id = project['notes'][0]['_id'] + note_path = '/projects/' + data.project_id + '/notes/' + note_id + r = api_as_user.get(note_path) + assert r.ok and json.loads(r.content)['_id'] == note_id + + # Modify note + modified_note = json.dumps({'text': 'modified test note'}) + r = api_as_user.put(note_path, data=modified_note) assert r.ok - assert json.loads(r.content)['text'] == 'modified test note' - r = requests.delete(url_get, verify=False) + + # Verify modified note + r = api_as_user.get(note_path) + assert r.ok and json.loads(r.content)['text'] == 'modified test note' + + # Delete note + r = api_as_user.delete(note_path) assert r.ok - r = requests.get(url_get, verify=False) - assert r.status_code == 404 + r = api_as_user.get(note_path) + assert r.status_code == 404 diff --git a/test/integration_tests/test_permissions.py b/test/integration_tests/test_permissions.py index f26cf736b..190946ac2 100644 --- a/test/integration_tests/test_permissions.py +++ b/test/integration_tests/test_permissions.py @@ -1,76 +1,83 @@ -import requests import json -import warnings -from nose.tools import with_setup +import time +import pytest import logging log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -log.setLevel(logging.INFO) -warnings.filterwarnings('ignore') -adm_user = 'admin@user.com' -user = 'test@user.com' -test_data = type('',(object,),{})() -base_url = 'https://localhost:8443/api' -def _build_url(_id=None, requestor=adm_user, site='local'): - if _id is None: - url = test_data.proj_url + '?user=' + requestor - else: - url = test_data.proj_url + '/' + site + '/' + _id + '?user=' + requestor - return url +def test_permissions(with_a_group_and_a_project, api_as_admin): + data = with_a_group_and_a_project + permissions_path = '/projects/' + data.project_id + '/permissions' + user_1_local_path = permissions_path + '/local/' + data.user_1 + user_2_local_path = permissions_path + '/local/' + data.user_2 + user_2_another_path = permissions_path + '/another/' + data.user_2 + # GET is not allowed for general permissions path + r = api_as_admin.get(permissions_path) + assert r.status_code == 405 -def setup_db(): - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - test_data.pid = json.loads(r.content)['_id'] - assert r.ok - log.debug('pid = \'{}\''.format(test_data.pid)) - test_data.proj_url = base_url + '/projects/{}/permissions'.format(test_data.pid) - -def teardown_db(): - r = requests.delete(base_url + '/projects/' + test_data.pid + '?user=admin@user.com&root=true', verify=False) - assert r.ok - -@with_setup(setup_db, teardown_db) -def test_permissions(): - url_post = _build_url() - url_get = _build_url(user) - r = requests.get(url_get, verify=False) - assert r.status_code == 404 - data = { - '_id': user, + # Add permissions for user 1 + payload = json.dumps({ + '_id': data.user_1, 'site': 'local', 'access': 'ro' - } - r = requests.post(url_post, data = json.dumps(data), verify=False) + }) + r = api_as_admin.post(permissions_path, data=payload) assert r.ok - r = requests.get(url_get, verify=False) + + # Verify permissions for user 1 + r = api_as_admin.get(user_1_local_path) assert r.ok content = json.loads(r.content) - assert content['_id'] == user + assert content['_id'] == data.user_1 assert content['site'] == 'local' assert content['access'] == 'ro' - data = { + + # Update user 1 to have admin access + payload = json.dumps({ 'access': 'admin' - } - r = requests.put(url_get, data = json.dumps(data), verify=False) + }) + r = api_as_admin.put(user_1_local_path, data=payload) + assert r.ok + + # Add user 2 to have ro access + payload = json.dumps({ + '_id': data.user_2, + 'site': 'local', + 'access': 'ro' + }) + r = api_as_admin.post(permissions_path, data=payload) + assert r.ok + + # Attempt to change user 2's id to user 1 + payload = json.dumps({ + '_id': data.user_1 + }) + r = api_as_admin.put(user_2_local_path, data=payload) + assert r.status_code == 404 + + # Change user 2's site + payload = json.dumps({ + 'site': 'another' + }) + r = api_as_admin.put(user_2_local_path, data=payload) assert r.ok - r = requests.get(url_get, verify=False) + + # Verify user 2's site changed + r = api_as_admin.get(user_2_another_path) assert r.ok content = json.loads(r.content) - assert content['_id'] == user - assert content['site'] == 'local' - assert content['access'] == 'admin' - r = requests.delete(url_get, verify=False) + assert content['_id'] == data.user_2 + assert content['site'] == 'another' + assert content['access'] == 'ro' + + # Delete user 2 + r = api_as_admin.delete(user_2_another_path) assert r.ok - r = requests.get(url_get, verify=False) + + # Ensure user 2 is gone + r = api_as_admin.get(user_2_another_path) assert r.status_code == 404 diff --git a/test/integration_tests/test_roles.py b/test/integration_tests/test_roles.py index e577792a9..e6dc78dc9 100644 --- a/test/integration_tests/test_roles.py +++ b/test/integration_tests/test_roles.py @@ -1,61 +1,92 @@ -import requests import json -from pprint import pprint - -import warnings -warnings.filterwarnings('ignore') - -base_url = 'https://localhost:8443/api/groups/scitran/roles' - -def _build_url_and_payload(method, user, access, requestor, site='local'): - if method == 'POST': - url = base_url + '?user=' + requestor - payload = { - '_id': user, - 'site': site, - 'access': access - } - return url, json.dumps(payload) - else: - url = base_url + '/' + site + '/' + user + '?user=' + requestor - return url, None - -adm_user = 'admin@user.com' -user = 'test@user.com' - -def test_roles(): - url_get, _ = _build_url_and_payload('GET', user, None, adm_user) - r = requests.get(url_get, verify=False) +import time +import pytest + + +@pytest.fixture() +def with_a_group_and_a_user(data_builder, api_as_admin, request, bunch): + user_id = 'other@user.com' + group_id = 'test_group_' + str(int(time.time() * 1000)) + data_builder.create_group(group_id) + + payload = json.dumps({ + '_id': user_id, + 'firstname': 'Other', + 'lastname': 'User', + }) + r = api_as_admin.post('/users', data=payload) + assert r.ok + + def teardown_db(): + data_builder.delete_group(group_id) + api_as_admin.delete('/users/' + user_id) + + request.addfinalizer(teardown_db) + + fixture_data = bunch.create() + fixture_data.user_id = user_id + fixture_data.group_id = group_id + return fixture_data + + +def create_role_payload(user, site, access): + return json.dumps({ + '_id': user, + 'site': site, + 'access': access + }) + + +def test_roles(api_as_admin, with_a_group_and_a_user, api_accessor): + data = with_a_group_and_a_user + api_as_other_user = api_accessor(data.user_id) + + roles_path = '/groups/' + data.group_id + '/roles' + local_user_roles_path = roles_path + '/local/' + data.user_id + admin_user_roles_path = roles_path + '/local/' + 'admin@user.com' + + # Cannot retrieve roles that don't exist + r = api_as_admin.get(local_user_roles_path) assert r.status_code == 404 - url_post, payload = _build_url_and_payload('POST', user, 'rw', adm_user) - r = requests.post(url_post, data=payload, verify=False) + # Create role for user + payload = create_role_payload(data.user_id, 'local', 'rw') + r = api_as_admin.post(roles_path, data=payload) assert r.ok - r = requests.get(url_get, verify=False) + + # Verify new user role + r = api_as_admin.get(local_user_roles_path) assert r.ok content = json.loads(r.content) assert content['access'] == 'rw' - assert content['_id'] == user + assert content['_id'] == data.user_id - url_get_not_auth, _ = _build_url_and_payload('GET', adm_user, None, user) - r = requests.get(url_get_not_auth, verify=False) + # 'rw' users cannot access other user roles + r = api_as_other_user.get(admin_user_roles_path) assert r.status_code == 403 - payload = json.dumps({'access':'admin'}) - r = requests.put(url_get, data=payload, verify=False) + # Upgrade user to admin + payload = json.dumps({'access': 'admin'}) + r = api_as_admin.put(local_user_roles_path, data=payload) assert r.ok - r = requests.get(url_get_not_auth, verify=False) + # User should now be able to access other roles + r = api_as_other_user.get(admin_user_roles_path) assert r.ok - payload = json.dumps({'access':'rw'}) - r = requests.put(url_get, data=payload, verify=False) + # Change user back to 'rw' access + payload = json.dumps({'access': 'rw'}) + r = api_as_admin.put(local_user_roles_path, data=payload) assert r.ok - r = requests.get(url_get_not_auth, verify=False) + # User is now forbidden again + r = api_as_other_user.get(admin_user_roles_path) assert r.status_code == 403 - r = requests.delete(url_get, verify=False) + # Delete role + r = api_as_admin.delete(local_user_roles_path) assert r.ok - r = requests.get(url_get, verify=False) + + # Verify delete + r = api_as_admin.get(local_user_roles_path) assert r.status_code == 404 diff --git a/test/integration_tests/test_tags.py b/test/integration_tests/test_tags.py index eb19c8a17..a8d0b9438 100644 --- a/test/integration_tests/test_tags.py +++ b/test/integration_tests/test_tags.py @@ -1,94 +1,67 @@ -import requests import json -import warnings -from nose.tools import with_setup import logging log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -log.setLevel(logging.INFO) -warnings.filterwarnings('ignore') -adm_user = 'admin@user.com' -test_data = type('',(object,),{})() -base_url = 'https://localhost:8443/api' -def _build_url_and_payload(method, tag, newtag=None, requestor=adm_user): - if method == 'POST': - url = test_data.proj_url + '?user=' + requestor - payload = json.dumps({'value': tag}) - else: - url = test_data.proj_url + '/' + tag + '?user=' + requestor - payload = json.dumps({'value': newtag}) - return url, payload - - -def setup_db(): - payload = { - 'group': 'unknown', - 'label': 'SciTran/Testing', - 'public': False - } - payload = json.dumps(payload) - r = requests.post(base_url + '/projects?user=admin@user.com&root=true', data=payload, verify=False) - test_data.pid = json.loads(r.content)['_id'] - assert r.ok - log.debug('pid = \'{}\''.format(test_data.pid)) - test_data.proj_url = base_url + '/projects/{}/tags'.format(test_data.pid) - -def teardown_db(): - r = requests.delete(base_url + '/projects/' + test_data.pid + '?user=admin@user.com&root=true', verify=False) - assert r.ok - -@with_setup(setup_db, teardown_db) -def test_tags(): +def test_tags(with_a_group_and_a_project, api_as_admin): + data = with_a_group_and_a_project tag = 'test_tag' new_tag = 'new_test_tag' other_tag = 'other_test_tag' - url_get_tag, _ = _build_url_and_payload('GET', tag) - url_get_new, _ = _build_url_and_payload('GET', new_tag) - url_get_other, _ = _build_url_and_payload('GET', other_tag) - r = requests.get(url_get_tag, verify=False) + tags_path = '/projects/' + data.project_id + '/tags' + tag_path = tags_path + '/' + tag + new_tag_path = tags_path + '/' + new_tag + other_tag_path = tags_path + '/' + other_tag + + # Add tag and verify + r = api_as_admin.get(tag_path) assert r.status_code == 404 - url_post, payload = _build_url_and_payload('POST', tag) - r = requests.post(url_post, data=payload, verify=False) + payload = json.dumps({'value': tag}) + r = api_as_admin.post(tags_path, data=payload) assert r.ok - r = requests.get(url_get_tag, verify=False) + r = api_as_admin.get(tag_path) assert r.ok assert json.loads(r.content) == tag - url_post, payload = _build_url_and_payload('POST', new_tag) - r = requests.post(url_post, data=payload, verify=False) + # Add new tag and verify + payload = json.dumps({'value': new_tag}) + r = api_as_admin.post(tags_path, data=payload) assert r.ok - url_post, payload = _build_url_and_payload('POST', new_tag) - r = requests.post(url_post, data=payload, verify=False) + # Add a duplicate tag, returns 404 + payload = json.dumps({'value': new_tag}) + r = api_as_admin.post(tags_path, data=payload) assert r.status_code == 404 - r = requests.get(url_get_new, verify=False) + r = api_as_admin.get(new_tag_path) assert r.ok assert json.loads(r.content) == new_tag - url_put, payload = _build_url_and_payload('PUT', tag, new_tag) - r = requests.put(url_put, data=payload, verify=False) + # Attempt to update tag, returns 404 + payload = json.dumps({'value': new_tag}) + r = api_as_admin.put(tag_path, data=payload) assert r.status_code == 404 - r = requests.get(url_get_other, verify=False) + # Update existing tag to other_tag + r = api_as_admin.get(other_tag_path) assert r.status_code == 404 - url_put, payload = _build_url_and_payload('PUT', tag, other_tag) - r = requests.put(url_put, data=payload, verify=False) + payload = json.dumps({'value': other_tag}) + r = api_as_admin.put(tag_path, data=payload) assert r.ok - r = requests.get(url_get_other, verify=False) + r = api_as_admin.get(other_tag_path) assert r.ok assert json.loads(r.content) == other_tag - - r = requests.get(url_get_tag, verify=False) + r = api_as_admin.get(tag_path) assert r.status_code == 404 - r = requests.delete(url_get_other, verify=False) # url for 'DELETE' is the same as the one for 'GET' + + # Cleanup + r = api_as_admin.delete(other_tag_path) # url for 'DELETE' is the same as the one for 'GET' assert r.ok - r = requests.get(url_get_other, verify=False) + r = api_as_admin.get(other_tag_path) assert r.status_code == 404 - r = requests.delete(url_get_new, verify=False) # url for 'DELETE' is the same as the one for 'GET' + r = api_as_admin.delete(new_tag_path) # url for 'DELETE' is the same as the one for 'GET' assert r.ok - r = requests.get(url_get_new, verify=False) + r = api_as_admin.get(new_tag_path) assert r.status_code == 404 diff --git a/test/integration_tests/test_users.py b/test/integration_tests/test_users.py index 57b30b061..09d6f5956 100644 --- a/test/integration_tests/test_users.py +++ b/test/integration_tests/test_users.py @@ -1,35 +1,38 @@ -import requests import json import logging + log = logging.getLogger(__name__) sh = logging.StreamHandler() log.addHandler(sh) -requests.packages.urllib3.disable_warnings() -base_url = 'https://localhost:8443/api' +def test_users(api_as_admin): + new_user_id = 'new@user.com' -def test_users(): - _id = 'new@user.com' - r = requests.get(base_url + '/users/self?user=admin@user.com', verify=False) + # Get self + r = api_as_admin.get('/users/self') assert r.ok - r = requests.get(base_url + '/users/' + _id + '?user=admin@user.com&root=true', verify=False) + + # Add new user + r = api_as_admin.get('/users/' + new_user_id) assert r.status_code == 404 - payload = { - '_id': _id, + payload = json.dumps({ + '_id': new_user_id, 'firstname': 'New', 'lastname': 'User', - } - payload = json.dumps(payload) - r = requests.post(base_url + '/users?user=admin@user.com&root=true', data=payload, verify=False) + }) + r = api_as_admin.post('/users', data=payload) assert r.ok - r = requests.get(base_url + '/users/' + _id + '?user=admin@user.com&root=true', verify=False) + r = api_as_admin.get('/users/' + new_user_id) assert r.ok - payload = { + + # Modify existing user + payload = json.dumps({ 'firstname': 'Realname' - } - payload = json.dumps(payload) - r = requests.put(base_url + '/users/' + _id + '?user=admin@user.com&root=true', data=payload, verify=False) + }) + r = api_as_admin.put('/users/' + new_user_id, data=payload) assert r.ok - r = requests.delete(base_url + '/users/' + _id + '?user=admin@user.com&root=true', verify=False) + + # Cleanup + r = api_as_admin.delete('/users/' + new_user_id) assert r.ok diff --git a/test/requirements-integration-test.txt b/test/requirements-integration-test.txt new file mode 100644 index 000000000..6120ac2b0 --- /dev/null +++ b/test/requirements-integration-test.txt @@ -0,0 +1,22 @@ +# Development packages +coverage==4.0.3 +coveralls==1.1 +nose==1.3.7 +PasteScript==2.0.2 +pylint==1.5.3 +pytest==2.8.5 +pytest-cov==2.2.0 +pytest-watch==3.8.0 +pymongo==3.2 + +# Production packages +enum==0.4.6 +jsonschema==2.5.1 +Markdown==2.6.5 +pyOpenSSL==0.15.1 +python-dateutil==2.4.2 +pytz==2015.7 +requests==2.9.1 +rfc3987==1.3.4 +webapp2==2.5.2 +WebOb==1.5.1