From dc9df4f077510fbfe55b6bf39c9d2ad68b1c933e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Dec 2021 08:21:38 +0000 Subject: [PATCH 1/5] Bump aiohttp from 3.7.4.post0 to 3.8.1 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.7.4.post0 to 3.8.1. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.7.4.post0...v3.8.1) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 71015ea9..0d05f1ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.7.4.post0 +aiohttp==3.8.1 aiohttp-cors==0.7.0 asyncpg==0.25.0 jsonschema==3.2.0; python_version < '3.7' diff --git a/setup.py b/setup.py index 0b0f2a3a..1ef82286 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ ], install_requires=[ "asyncpg==0.25.0", - "aiohttp==3.7.4.post0", + "aiohttp==3.8.1", "Authlib==0.15.5", "aiohttp-cors==0.7.0", "jsonschema==3.2.0; python_version < '3.7'", From d0e703f56947328b1357b860ba764fadc65085ba Mon Sep 17 00:00:00 2001 From: Stefan Negru Date: Fri, 31 Dec 2021 11:27:37 +0200 Subject: [PATCH 2/5] drop support for python 3.6 --- .github/workflows/s2i-build.yml | 6 +++--- README.md | 2 +- beacon_api/app.py | 12 +++++++++--- docs/instructions.rst | 2 +- requirements.txt | 12 ++++-------- setup.py | 17 ++++++----------- tox.ini | 5 ++--- 7 files changed, 26 insertions(+), 30 deletions(-) diff --git a/.github/workflows/s2i-build.yml b/.github/workflows/s2i-build.yml index a27edff7..e969f038 100644 --- a/.github/workflows/s2i-build.yml +++ b/.github/workflows/s2i-build.yml @@ -18,9 +18,9 @@ jobs: - name: Install requirements run: | - wget https://github.com/openshift/source-to-image/releases/download/v1.2.0/source-to-image-v1.2.0-2a579ecd-linux-amd64.tar.gz - tar -xvf source-to-image-v1.2.0-2a579ecd-linux-amd64.tar.gz + wget https://github.com/openshift/source-to-image/releases/download/v1.3.1/source-to-image-v1.3.1-a5a77147-linux-amd64.tar.gz + tar -xvf source-to-image-v1.3.1-a5a77147-linux-amd64.tar.gz sudo cp s2i /usr/local/bin - name: Build image run: | - s2i build . centos/python-36-centos7 cscfi/beacon-python + s2i build . centos/python-38-centos7 cscfi/beacon-python diff --git a/README.md b/README.md index 7fc1b721..0a9df73a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Documentation: https://beacon-python.readthedocs.io ### Quick start `beacon-python` Web Server requires: -* Python 3.6+; +* Python 3.8+; * running DB [PostgreSQL Server](https://www.postgresql.org/) 9.6+ (recommended 11.6). ```shell diff --git a/beacon_api/app.py b/beacon_api/app.py index 0931b469..702fe02b 100644 --- a/beacon_api/app.py +++ b/beacon_api/app.py @@ -125,11 +125,17 @@ def main(): # sslcontext.load_cert_chain(ssl_certfile, ssl_keyfile) # sslcontext = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) # sslcontext.check_hostname = False - web.run_app(init(), host=os.environ.get("HOST", "0.0.0.0"), port=os.environ.get("PORT", "5050"), shutdown_timeout=0, ssl_context=None) # nosec # nosec + web.run_app( + init(), + host=os.environ.get("HOST", "0.0.0.0"), + port=os.environ.get("PORT", "5050"), + shutdown_timeout=0, + ssl_context=None, + ) # nosec # nosec if __name__ == "__main__": - if sys.version_info < (3, 6): - LOG.error("beacon-python requires python 3.6") + if sys.version_info < (3, 8): + LOG.error("beacon-python requires python 3.8") sys.exit(1) main() diff --git a/docs/instructions.rst b/docs/instructions.rst index 23dbcc5d..72a5e63a 100644 --- a/docs/instructions.rst +++ b/docs/instructions.rst @@ -3,7 +3,7 @@ Instructions .. note:: In order to run ``beacon-python`` Web Server requirements are as specified below: - * Python 3.6+; + * Python 3.8+; * running DB `PostgreSQL Server `_ 9.6+ (recommended 11.6). .. _env-setup: diff --git a/requirements.txt b/requirements.txt index 0d05f1ed..b51bfea7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,12 @@ aiohttp==3.8.1 aiohttp-cors==0.7.0 asyncpg==0.25.0 -jsonschema==3.2.0; python_version < '3.7' -jsonschema==4.3.2; python_version >= '3.7' +jsonschema==4.3.2 Cython==0.29.26 -cyvcf2==0.10.1; python_version < '3.7' -cyvcf2; python_version >= '3.7' -uvloop==0.14.0; python_version < '3.7' -uvloop==0.16.0; python_version >= '3.7' +cyvcf2==0.30.14 +uvloop==0.16.0 aiocache==0.11.1 -ujson==4.3.0; python_version < '3.7' -ujson==5.1.0; python_version >= '3.7' +ujson==5.1.0 aiomcache==0.6.0 Authlib==0.15.5 gunicorn==20.1.0 diff --git a/setup.py b/setup.py index 1ef82286..2ed568a8 100644 --- a/setup.py +++ b/setup.py @@ -33,31 +33,26 @@ "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Scientific/Engineering :: Bio-Informatics", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], install_requires=[ "asyncpg==0.25.0", "aiohttp==3.8.1", "Authlib==0.15.5", "aiohttp-cors==0.7.0", - "jsonschema==3.2.0; python_version < '3.7'", - "jsonschema==4.3.2; python_version >= '3.7'", + "jsonschema==4.3.2", "gunicorn==20.1.0", - "uvloop==0.14.0; python_version < '3.7'", - "uvloop==0.16.0; python_version >= '3.7'", - "cyvcf2==0.10.1; python_version < '3.7'", - "cyvcf2; python_version >= '3.7'", + "uvloop==0.16.0", + "cyvcf2==0.30.14", "aiocache==0.11.1", "aiomcache==0.6.0", - "ujson==4.3.0; python_version < '3.7'", - "ujson==5.1.0; python_version >= '3.7'", + "ujson==5.1.0", ], extras_require={ "vcf": [ - "cyvcf2==0.10.1; python_version < '3.7'", "numpy==1.21.5", - "cyvcf2; python_version >= '3.7'", + "cyvcf2==0.30.14", "Cython==0.29.26", ], "test": [ diff --git a/tox.ini b/tox.ini index 5961f548..2abdedb1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{36,38},flake8,docs,bandit,unit_tests,mypy, black +envlist = flake8,docs,bandit,unit_tests,mypy skipsdist = True [flake8] @@ -56,5 +56,4 @@ commands = py.test -x --cov=beacon_api tests/ --cov-fail-under=80 [gh-actions] python = - 3.6: unit_tests - 3.8: flake8, black, unit_tests, docs, bandit, mypy + 3.8: flake8, unit_tests, docs, bandit, mypy From 5a1974b57592693ac10cfd61b1646b2d18c9da55 Mon Sep 17 00:00:00 2001 From: Stefan Negru Date: Fri, 31 Dec 2021 18:01:01 +0200 Subject: [PATCH 3/5] remove asynctest --- beacon_api/api/exceptions.py | 18 +- beacon_api/app.py | 4 +- requirements.txt | 1 - setup.py | 2 - tests/test_app.py | 140 +++---- tests/test_basic.py | 43 +- tests/test_data_query.py | 197 ++-------- tests/test_db_load.py | 744 +++++++++++++++++------------------ tests/test_mate_name.py | 101 +---- tests/test_response.py | 24 +- 10 files changed, 508 insertions(+), 766 deletions(-) diff --git a/beacon_api/api/exceptions.py b/beacon_api/api/exceptions.py index 18760737..8ebb7e3b 100644 --- a/beacon_api/api/exceptions.py +++ b/beacon_api/api/exceptions.py @@ -35,7 +35,17 @@ def process_exception_data(request: Dict, host: str, error_code: int, error: str # include datasetIds only if they are specified # as per specification if they don't exist all datatsets will be queried # Only one of `alternateBases` or `variantType` is required, validated by schema - oneof_fields = ["alternateBases", "variantType", "start", "end", "startMin", "startMax", "endMin", "endMax", "datasetIds"] + oneof_fields = [ + "alternateBases", + "variantType", + "start", + "end", + "startMin", + "startMax", + "endMin", + "endMax", + "datasetIds", + ] data["alleleRequest"].update({k: request.get(k) for k in oneof_fields if k in request}) return data @@ -65,11 +75,7 @@ class BeaconUnauthorised(web.HTTPUnauthorized): def __init__(self, request: Dict, host: str, error: str, error_message: str) -> None: """Return custom unauthorized exception.""" data = process_exception_data(request, host, 401, error) - headers_401 = { - "WWW-Authenticate": f'Bearer realm="{CONFIG_INFO.url}"\n\ - error="{error}"\n\ - error_description="{error_message}"' - } + headers_401 = {"WWW-Authenticate": f"""Bearer realm=\"{CONFIG_INFO.url}\",error=\"{error},\" error_description=\"{error_message}\""""} super().__init__( content_type="application/json", text=ujson.dumps(data, escape_forward_slashes=False), diff --git a/beacon_api/app.py b/beacon_api/app.py index 702fe02b..357e5179 100644 --- a/beacon_api/app.py +++ b/beacon_api/app.py @@ -127,11 +127,11 @@ def main(): # sslcontext.check_hostname = False web.run_app( init(), - host=os.environ.get("HOST", "0.0.0.0"), + host=os.environ.get("HOST", "0.0.0.0"), # nosec port=os.environ.get("PORT", "5050"), shutdown_timeout=0, ssl_context=None, - ) # nosec # nosec + ) if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt index b51bfea7..8f730af0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,5 @@ cyvcf2==0.30.14 uvloop==0.16.0 aiocache==0.11.1 ujson==5.1.0 -aiomcache==0.6.0 Authlib==0.15.5 gunicorn==20.1.0 diff --git a/setup.py b/setup.py index 2ed568a8..26bf6782 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,6 @@ "uvloop==0.16.0", "cyvcf2==0.30.14", "aiocache==0.11.1", - "aiomcache==0.6.0", "ujson==5.1.0", ], extras_require={ @@ -63,7 +62,6 @@ "tox==3.24.4", "flake8==4.0.1", "flake8-docstrings==1.6.0", - "asynctest==0.13.0", "aioresponses==0.7.2", "black==21.12b0", ], diff --git a/tests/test_app.py b/tests/test_app.py index 38d6901a..8a1f0d7d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,10 +1,7 @@ import unittest -from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop -from aiohttp import web -from beacon_api.app import init, main, initialize -from unittest import mock +from aiohttp.test_utils import AioHTTPTestCase +from beacon_api.app import init, initialize import asyncpg -import asynctest import json from authlib.jose import jwt import os @@ -17,7 +14,13 @@ def generate_token(issuer): """Mock ELIXIR AAI token.""" - pem = {"kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"} + pem = { + "kty": "oct", + "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", + "use": "sig", + "alg": "HS256", + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", + } header = {"jku": "http://test.csc.fi/jwk", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "alg": "HS256"} payload = {"iss": issuer, "aud": "audience", "exp": 9999999999, "sub": "smth@smth.org"} token = jwt.encode(header, payload, pem).decode("utf-8") @@ -26,7 +29,13 @@ def generate_token(issuer): def generate_bad_token(): """Mock AAI token.""" - pem = {"kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"} + pem = { + "kty": "oct", + "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", + "use": "sig", + "alg": "HS256", + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", + } header = {"jku": "http://test.csc.fi/jwk", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "alg": "HS256"} payload = {"iss": "bad_issuer", "aud": "audience", "exp": 0, "sub": "smth@elixir-europe.org"} token = jwt.encode(header, payload, pem).decode("utf-8") @@ -35,7 +44,7 @@ def generate_bad_token(): async def create_db_mock(app): """Mock the db connection pool.""" - app["pool"] = asynctest.mock.Mock(asyncpg.create_pool()) + app["pool"] = unittest.mock.Mock(asyncpg.create_pool()) return app @@ -50,7 +59,7 @@ class AppTestCase(AioHTTPTestCase): Testing web app endpoints. """ - @asynctest.mock.patch("beacon_api.app.initialize", side_effect=create_db_mock) + @unittest.mock.patch("beacon_api.app.initialize", side_effect=create_db_mock) async def get_application(self, pool_mock): """Retrieve web Application for test.""" token, public_key = generate_token("http://test.csc.fi") @@ -60,34 +69,30 @@ async def get_application(self, pool_mock): self.env.set("TOKEN", token) return await init() - @unittest_run_loop - async def tearDown(self): + async def tearDownAsync(self): """Finish up tests.""" self.env.unset("PUBLIC_KEY") self.env.unset("TOKEN") await caches.get("default").delete("jwk_key") - @unittest_run_loop async def test_beacon_info(self): """Test the Beacon info endpoint. The status should always be 200. """ - with asynctest.mock.patch("beacon_api.app.beacon_info", return_value={"id": "value"}): + with unittest.mock.patch("beacon_api.app.beacon_info", return_value={"id": "value"}): resp = await self.client.request("GET", "/") self.assertEqual(200, resp.status) - @unittest_run_loop async def test_ga4gh_info(self): """Test the GA4GH Discovery info endpoint. The status should always be 200. """ - with asynctest.mock.patch("beacon_api.app.ga4gh_info", return_value={"id": "value"}): + with unittest.mock.patch("beacon_api.app.ga4gh_info", return_value={"id": "value"}): resp = await self.client.request("GET", "/service-info") self.assertEqual(200, resp.status) - @unittest_run_loop async def test_post_info(self): """Test the info endpoint with POST. @@ -96,7 +101,6 @@ async def test_post_info(self): resp = await self.client.request("POST", "/") self.assertEqual(405, resp.status) - @unittest_run_loop async def test_post_service_info(self): """Test the service-info endpoint with POST. @@ -105,19 +109,16 @@ async def test_post_service_info(self): resp = await self.client.request("POST", "/service-info") self.assertEqual(405, resp.status) - @unittest_run_loop async def test_empty_get_query(self): """Test empty GET query endpoint.""" resp = await self.client.request("GET", "/query") self.assertEqual(400, resp.status) - @unittest_run_loop async def test_empty_post_query(self): """Test empty POST query endpoint.""" resp = await self.client.request("POST", "/query", data=json.dumps({})) self.assertEqual(400, resp.status) - @unittest_run_loop async def test_bad_start_post_query(self): """Test bad start combination POST query endpoint.""" bad_start = { @@ -134,7 +135,6 @@ async def test_bad_start_post_query(self): resp = await self.client.request("POST", "/query", data=json.dumps(bad_start)) self.assertEqual(400, resp.status) - @unittest_run_loop async def test_bad_start2_post_query(self): """Test bad start combination 2 POST query endpoint.""" bad_start = { @@ -151,7 +151,6 @@ async def test_bad_start2_post_query(self): resp = await self.client.request("POST", "/query", data=json.dumps(bad_start)) self.assertEqual(400, resp.status) - @unittest_run_loop async def test_bad_startend_post_query(self): """Test end smaller than start POST query endpoint.""" bad_start = { @@ -166,7 +165,6 @@ async def test_bad_startend_post_query(self): resp = await self.client.request("POST", "/query", data=json.dumps(bad_start)) self.assertEqual(400, resp.status) - @unittest_run_loop async def test_bad_startminmax_post_query(self): """Test start min greater than start Max POST query endpoint.""" bad_start = { @@ -181,7 +179,6 @@ async def test_bad_startminmax_post_query(self): resp = await self.client.request("POST", "/query", data=json.dumps(bad_start)) self.assertEqual(400, resp.status) - @unittest_run_loop async def test_bad_endminmax_post_query(self): """Test end min greater than start Max POST query endpoint.""" bad_start = { @@ -196,19 +193,24 @@ async def test_bad_endminmax_post_query(self): resp = await self.client.request("POST", "/query", data=json.dumps(bad_start)) self.assertEqual(400, resp.status) - @asynctest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) - @asynctest.mock.patch("beacon_api.app.query_request_handler") - @unittest_run_loop + @unittest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) + @unittest.mock.patch("beacon_api.app.query_request_handler") async def test_good_start_post_query(self, mock_handler, mock_object): """Test good start combination POST query endpoint.""" - good_start = {"referenceName": "MT", "start": 10, "referenceBases": "T", "variantType": "MNP", "assemblyId": "GRCh38", "includeDatasetResponses": "HIT"} + good_start = { + "referenceName": "MT", + "start": 10, + "referenceBases": "T", + "variantType": "MNP", + "assemblyId": "GRCh38", + "includeDatasetResponses": "HIT", + } mock_handler.side_effect = json.dumps(good_start) resp = await self.client.request("POST", "/query", data=json.dumps(good_start)) self.assertEqual(200, resp.status) - @asynctest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) - @asynctest.mock.patch("beacon_api.app.query_request_handler") - @unittest_run_loop + @unittest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) + @unittest.mock.patch("beacon_api.app.query_request_handler") async def test_good_start2_post_query(self, mock_handler, mock_object): """Test good start combination 2 POST query endpoint.""" good_start = { @@ -224,9 +226,8 @@ async def test_good_start2_post_query(self, mock_handler, mock_object): resp = await self.client.request("POST", "/query", data=json.dumps(good_start)) self.assertEqual(200, resp.status) - @asynctest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) - @asynctest.mock.patch("beacon_api.app.query_request_handler") - @unittest_run_loop + @unittest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) + @unittest.mock.patch("beacon_api.app.query_request_handler") async def test_good_start3_post_query(self, mock_handler, mock_object): """Test good start combination 3 POST query endpoint.""" good_start = { @@ -242,53 +243,46 @@ async def test_good_start3_post_query(self, mock_handler, mock_object): resp = await self.client.request("POST", "/query", data=json.dumps(good_start)) self.assertEqual(200, resp.status) - @unittest_run_loop async def test_unauthorized_no_token_post_query(self): """Test unauthorized POST query endpoint, with no token.""" resp = await self.client.request("POST", "/query", data=json.dumps(PARAMS), headers={"Authorization": "Bearer"}) self.assertEqual(401, resp.status) - @unittest_run_loop async def test_unauthorized_token_post_query(self): """Test unauthorized POST query endpoint, bad token.""" resp = await self.client.request("POST", "/query", data=json.dumps(PARAMS), headers={"Authorization": f"Bearer {self.bad_token}"}) self.assertEqual(403, resp.status) - @unittest_run_loop async def test_invalid_scheme_get_query(self): """Test unauthorized GET query endpoint, invalid scheme.""" params = "?assemblyId=GRCh38&referenceName=1&start=10000&referenceBases=A&alternateBases=T&datasetIds=dataset1" resp = await self.client.request("GET", f"/query{params}", headers={"Authorization": "SMTH x"}) self.assertEqual(401, resp.status) - @asynctest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) - @asynctest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) - @unittest_run_loop + @unittest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) + @unittest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) async def test_valid_token_get_query(self, mock_handler, mock_object): """Test valid token GET query endpoint.""" token = os.environ.get("TOKEN") resp = await self.client.request("POST", "/query", data=json.dumps(PARAMS), headers={"Authorization": f"Bearer {token}"}) self.assertEqual(200, resp.status) - @unittest_run_loop async def test_bad_json_post_query(self): """Test bad json POST query endpoint.""" resp = await self.client.request("POST", "/query", data="") self.assertEqual(500, resp.status) - @asynctest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) - @asynctest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) - @unittest_run_loop + @unittest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) + @unittest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) async def test_valid_get_query(self, mock_handler, mock_object): """Test valid GET query endpoint.""" params = "?assemblyId=GRCh38&referenceName=1&start=10000&referenceBases=A&alternateBases=T" - with asynctest.mock.patch("beacon_api.app.initialize", side_effect=create_db_mock): + with unittest.mock.patch("beacon_api.app.initialize", side_effect=create_db_mock): resp = await self.client.request("GET", f"/query{params}") self.assertEqual(200, resp.status) - @asynctest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) - @asynctest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) - @unittest_run_loop + @unittest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) + @unittest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) async def test_valid_post_query(self, mock_handler, mock_object): """Test valid POST query endpoint.""" resp = await self.client.request("POST", "/query", data=json.dumps(PARAMS)) @@ -301,7 +295,13 @@ class AppTestCaseForbidden(AioHTTPTestCase): Testing web app for wrong issuer. """ - @asynctest.mock.patch("beacon_api.app.initialize", side_effect=create_db_mock) + async def tearDownAsync(self): + """Finish up tests.""" + self.env.unset("PUBLIC_KEY") + self.env.unset("TOKEN") + await caches.get("default").delete("jwk_key") + + @unittest.mock.patch("beacon_api.app.initialize", side_effect=create_db_mock) async def get_application(self, pool_mock): """Retrieve web Application for test.""" token, public_key = generate_token("something") @@ -310,16 +310,8 @@ async def get_application(self, pool_mock): self.env.set("TOKEN", token) return await init() - @unittest_run_loop - async def tearDown(self): - """Finish up tests.""" - self.env.unset("PUBLIC_KEY") - self.env.unset("TOKEN") - await caches.get("default").delete("jwk_key") - - @asynctest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) - @asynctest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) - @unittest_run_loop + @unittest.mock.patch("beacon_api.app.parse_request_object", side_effect=mock_parse_request_object) + @unittest.mock.patch("beacon_api.app.query_request_handler", side_effect=json.dumps(PARAMS)) async def test_forbidden_token_get_query(self, mock_handler, mock_object): """Test forbidden GET query endpoint, invalid scheme.""" token = os.environ.get("TOKEN") @@ -327,39 +319,27 @@ async def test_forbidden_token_get_query(self, mock_handler, mock_object): self.assertEqual(403, resp.status) -class TestBasicFunctionsApp(asynctest.TestCase): +class TestBasicFunctionsApp(unittest.IsolatedAsyncioTestCase): """Test App Base. Testing basic functions from web app. """ - def setUp(self): - """Initialise fixtures.""" - pass - - def tearDown(self): - """Remove setup variables.""" - pass - - @mock.patch("beacon_api.app.web") - def test_main(self, mock_webapp): - """Should start the webapp.""" - main() - mock_webapp.run_app.assert_called() - - async def test_init(self): - """Test init type.""" - server = await init() - self.assertIs(type(server), web.Application) + async def test_servinit(self): + """Test server initialization function execution.""" + # Don't really need much testing here, if the server initialization + # executes to the end all is fine. + app = await init() + self.assertTrue(app is not None) - @asynctest.mock.patch("beacon_api.app.set_cors") + @unittest.mock.patch("beacon_api.app.set_cors") async def test_initialize(self, mock_cors): """Test create db pool, should just return the result of init_db_pool. We will mock the init_db_pool, thus we assert we just call it. """ app = {} - with asynctest.mock.patch("beacon_api.app.init_db_pool") as db_mock: + with unittest.mock.patch("beacon_api.app.init_db_pool") as db_mock: await initialize(app) db_mock.assert_called() diff --git a/tests/test_basic.py b/tests/test_basic.py index d2fd637b..575e9e9f 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,4 +1,4 @@ -import asynctest +import unittest import aiohttp from beacon_api.utils.db_load import parse_arguments, init_beacon_db, main from beacon_api.conf.config import init_db_pool @@ -70,7 +70,7 @@ async def load_datafile(self, vcf, datafile, datasetId, n=1000, min_ac=1): return ["datasetId", "variants"] -class TestBasicFunctions(asynctest.TestCase): +class TestBasicFunctions(unittest.IsolatedAsyncioTestCase): """Test supporting functions.""" def setUp(self): @@ -87,17 +87,17 @@ def test_parser(self): self.assertEqual(parsed.datafile, "/path/to/datafile.csv") self.assertEqual(parsed.metadata, "/path/to/metadata.json") - @asynctest.mock.patch("beacon_api.conf.config.asyncpg") + @unittest.mock.patch("beacon_api.conf.config.asyncpg") async def test_init_pool(self, db_mock): """Test database connection pool creation.""" - db_mock.return_value = asynctest.CoroutineMock(name="create_pool") - db_mock.create_pool = asynctest.CoroutineMock() + db_mock.return_value = unittest.mock.AsyncMock(name="create_pool") + db_mock.create_pool = unittest.mock.AsyncMock() await init_db_pool() db_mock.create_pool.assert_called() - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.BeaconDB") - @asynctest.mock.patch("beacon_api.utils.db_load.VCF") + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.BeaconDB") + @unittest.mock.patch("beacon_api.utils.db_load.VCF") async def test_init_beacon_db(self, mock_vcf, db_mock, mock_log): """Test beacon_init db call.""" db_mock.return_value = MockBeaconDB() @@ -112,9 +112,12 @@ async def test_init_beacon_db(self, mock_vcf, db_mock, mock_log): data = """MOCK VCF file""" datafile = self._dir.write("data.vcf", data.encode("utf-8")) await init_beacon_db([datafile, metafile]) - mock_log.info.mock_calls = ["Mark the database connection to be closed", "The database connection has been closed"] + mock_log.info.mock_calls = [ + "Mark the database connection to be closed", + "The database connection has been closed", + ] - @asynctest.mock.patch("beacon_api.utils.db_load.init_beacon_db") + @unittest.mock.patch("beacon_api.utils.db_load.init_beacon_db") def test_main_db(self, mock_init): """Test run asyncio main beacon init.""" main() @@ -318,7 +321,7 @@ def test_access_resolution_controlled_never_reached2(self): with self.assertRaises(aiohttp.web_exceptions.HTTPForbidden): access_resolution(request, token, host, [], [], ["8"]) - @asynctest.mock.patch("beacon_api.permissions.ga4gh.validate_passport") + @unittest.mock.patch("beacon_api.permissions.ga4gh.validate_passport") async def test_ga4gh_controlled(self, m_validation): """Test ga4gh permissions claim parsing.""" # Test: no passports, no permissions @@ -396,9 +399,9 @@ async def test_ga4gh_bona_fide(self): bona_fide_status = await get_ga4gh_bona_fide(passports_empty) self.assertEqual(bona_fide_status, False) # doesn't have bona fide - @asynctest.mock.patch("beacon_api.permissions.ga4gh.get_jwk") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.jwt") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.LOG") + @unittest.mock.patch("beacon_api.permissions.ga4gh.get_jwk") + @unittest.mock.patch("beacon_api.permissions.ga4gh.jwt") + @unittest.mock.patch("beacon_api.permissions.ga4gh.LOG") async def test_validate_passport(self, mock_log, m_jwt, m_jwk): """Test passport validation.""" m_jwk.return_value = "jwk" @@ -414,7 +417,7 @@ async def test_validate_passport(self, mock_log, m_jwt, m_jwk): # need to assert the log called mock_log.error.assert_called_with("Something went wrong when processing JWT tokens: 1") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.get_ga4gh_permissions") + @unittest.mock.patch("beacon_api.permissions.ga4gh.get_ga4gh_permissions") async def test_check_ga4gh_token(self, m_get_perms): """Test token scopes.""" # Test: no scope found @@ -441,10 +444,10 @@ async def test_decode_passport(self): self.assertEqual(header.get("alg"), "HS256") self.assertEqual(payload.get("iss"), "http://test.csc.fi") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.get_ga4gh_bona_fide") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.get_ga4gh_controlled") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.decode_passport") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.retrieve_user_data") + @unittest.mock.patch("beacon_api.permissions.ga4gh.get_ga4gh_bona_fide") + @unittest.mock.patch("beacon_api.permissions.ga4gh.get_ga4gh_controlled") + @unittest.mock.patch("beacon_api.permissions.ga4gh.decode_passport") + @unittest.mock.patch("beacon_api.permissions.ga4gh.retrieve_user_data") async def test_get_ga4gh_permissions(self, m_userinfo, m_decode, m_controlled, m_bonafide): """Test GA4GH permissions main function.""" # Test: no data (nothing) @@ -480,4 +483,4 @@ async def test_get_ga4gh_permissions(self, m_userinfo, m_decode, m_controlled, m if __name__ == "__main__": - asynctest.main() + unittest.main() diff --git a/tests/test_data_query.py b/tests/test_data_query.py index 1e5cc705..bae9cf01 100644 --- a/tests/test_data_query.py +++ b/tests/test_data_query.py @@ -1,13 +1,10 @@ -import asynctest -import aiohttp +import unittest from unittest import mock from beacon_api.utils.data_query import filter_exists, transform_record from beacon_api.utils.data_query import transform_misses, transform_metadata, find_datasets, add_handover -from beacon_api.utils.data_query import fetch_datasets_access, fetch_dataset_metadata, fetch_filtered_dataset from beacon_api.extensions.handover import make_handover from datetime import datetime from beacon_api.utils.data_query import handle_wildcard -from .test_db_load import Connection, ConnectionException class Record: @@ -72,17 +69,9 @@ def values(self): return self.data.values() -class TestDataQueryFunctions(asynctest.TestCase): +class TestDataQueryFunctions(unittest.IsolatedAsyncioTestCase): """Test Data Query functions.""" - def setUp(self): - """Set up.""" - pass - - def tearDown(self): - """Close database connection after tests.""" - pass - def test_filter_exists(self): """Test filtering hits and miss datasets.""" datasets = [{"exists": True, "name": "DATASET1"}, {"exists": False, "name": "DATASET2"}] @@ -107,7 +96,16 @@ def test_transform_record(self): "variantCount": 3, "variantType": "MNP", } - record = Record("PUBLIC", 0.009112875989879, referenceBases="CT", alternateBases="AT", start=10, end=12, variantCount=3, variantType="MNP") + record = Record( + "PUBLIC", + 0.009112875989879, + referenceBases="CT", + alternateBases="AT", + start=10, + end=12, + variantCount=3, + variantType="MNP", + ) result = transform_record(record) self.assertEqual(result, response) @@ -131,7 +129,11 @@ def test_transform_misses(self): def test_transform_metadata(self): """Test transform medata record.""" - response = {"createDateTime": "2018-10-20T20:33:40Z", "updateDateTime": "2018-10-20T20:33:40Z", "info": {"accessType": "PUBLIC"}} + response = { + "createDateTime": "2018-10-20T20:33:40Z", + "updateDateTime": "2018-10-20T20:33:40Z", + "info": {"accessType": "PUBLIC"}, + } record = Record( "PUBLIC", createDateTime=datetime.strptime("2018-10-20 20:33:40+00", "%Y-%m-%d %H:%M:%S+00"), @@ -144,7 +146,15 @@ def test_add_handover(self): """Test that add handover.""" # Test that the handover actually is added handovers = [{"handover1": "info"}, {"handover2": "url"}] - record = {"datasetId": "test", "referenceName": "22", "referenceBases": "A", "alternateBases": "C", "start": 10, "end": 11, "variantType": "SNP"} + record = { + "datasetId": "test", + "referenceName": "22", + "referenceBases": "A", + "alternateBases": "C", + "start": 10, + "end": 11, + "variantType": "SNP", + } with mock.patch("beacon_api.extensions.handover.make_handover", return_value=handovers): result = add_handover(record) record["datasetHandover"] = handovers @@ -159,7 +169,7 @@ def test_make_handover(self): self.assertIn("path1", result[0]["url"]) self.assertEqual(result[0]["description"], "desc1") - @asynctest.mock.patch("beacon_api.utils.data_query.fetch_filtered_dataset") + @unittest.mock.patch("beacon_api.utils.data_query.fetch_filtered_dataset") async def test_find_datasets(self, mock_filtered): """Test find datasets.""" mock_filtered.return_value = [] @@ -171,157 +181,6 @@ async def test_find_datasets(self, mock_filtered): result_all = await find_datasets(None, "GRCh38", None, "Y", "T", "C", [], token, "ALL") self.assertEqual(result_all, []) - async def test_datasets_access_call_public(self): - """Test db call of getting public datasets access.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = Connection(accessData=[{"accesstype": "PUBLIC", "datasetid": "mock:public:id"}]) - result = await fetch_datasets_access(pool, None) - # for now it can return a tuple of empty datasets - # in order to get a response we will have to mock it - # in Connection() class - self.assertEqual(result, (["mock:public:id"], [], [])) - - async def test_datasets_access_call_exception(self): - """Test db call of getting public datasets access with exception.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = ConnectionException() - with self.assertRaises(aiohttp.web_exceptions.HTTPInternalServerError): - await fetch_datasets_access(pool, None) - - async def test_datasets_access_call_registered(self): - """Test db call of getting registered datasets access.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = Connection(accessData=[{"accesstype": "REGISTERED", "datasetid": "mock:registered:id"}]) - result = await fetch_datasets_access(pool, None) - # for now it can return a tuple of empty datasets - # in order to get a response we will have to mock it - # in Connection() class - self.assertEqual(result, ([], ["mock:registered:id"], [])) - - async def test_datasets_access_call_controlled(self): - """Test db call of getting controlled datasets access.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = Connection(accessData=[{"accesstype": "CONTROLLED", "datasetid": "mock:controlled:id"}]) - result = await fetch_datasets_access(pool, None) - # for now it can return a tuple of empty datasets - # in order to get a response we will have to mock it - # in Connection() class - self.assertEqual(result, ([], [], ["mock:controlled:id"])) - - async def test_datasets_access_call_multiple(self): - """Test db call of getting controlled and public datasets access.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = Connection( - accessData=[{"accesstype": "CONTROLLED", "datasetid": "mock:controlled:id"}, {"accesstype": "PUBLIC", "datasetid": "mock:public:id"}] - ) - result = await fetch_datasets_access(pool, None) - # for now it can return a tuple of empty datasets - # in order to get a response we will have to mock it - # in Connection() class - self.assertEqual(result, (["mock:public:id"], [], ["mock:controlled:id"])) - - async def test_fetch_dataset_metadata_call(self): - """Test db call of getting datasets metadata.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = Connection() - result = await fetch_dataset_metadata(pool, None, None) - # for now it can return empty dataset - # in order to get a response we will have to mock it - # in Connection() class - self.assertEqual(result, []) - - async def test_fetch_dataset_metadata_call_exception(self): - """Test db call of getting datasets metadata with exception.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = ConnectionException() - with self.assertRaises(aiohttp.web_exceptions.HTTPInternalServerError): - await fetch_dataset_metadata(pool, None, None) - - async def test_fetch_filtered_dataset_call(self): - """Test db call for retrieving main data.""" - pool = asynctest.CoroutineMock() - db_response = { - "referenceBases": "", - "alternateBases": "", - "variantType": "", - "referenceName": "Chr38", - "frequency": 0, - "callCount": 0, - "sampleCount": 0, - "variantCount": 0, - "start": 0, - "end": 0, - "accessType": "PUBLIC", - "datasetId": "test", - } - pool.acquire().__aenter__.return_value = Connection(accessData=[db_response]) - assembly_id = "GRCh38" - position = (10, 20, None, None, None, None) - chromosome = 1 - reference = "A" - alternate = ("DUP", None) - result = await fetch_filtered_dataset(pool, assembly_id, position, chromosome, reference, alternate, None, None, False) - # for now it can return empty dataset - # in order to get a response we will have to mock it - # in Connection() class - expected = { - "referenceName": "Chr38", - "callCount": 0, - "sampleCount": 0, - "variantCount": 0, - "datasetId": "test", - "referenceBases": "", - "alternateBases": "", - "variantType": "", - "start": 0, - "end": 0, - "frequency": 0, - "info": {"accessType": "PUBLIC"}, - "datasetHandover": [ - { - "handoverType": {"id": "CUSTOM", "label": "Variants"}, - "description": "browse the variants matched by the query", - "url": "https://examplebrowser.org/dataset/test/browser/variant/Chr38-1--", - }, - { - "handoverType": {"id": "CUSTOM", "label": "Region"}, - "description": "browse data of the region matched by the query", - "url": "https://examplebrowser.org/dataset/test/browser/region/Chr38-1-1", - }, - { - "handoverType": {"id": "CUSTOM", "label": "Data"}, - "description": "retrieve information of the datasets", - "url": "https://examplebrowser.org/dataset/test/browser", - }, - ], - } - - self.assertEqual(result, [expected]) - - async def test_fetch_filtered_dataset_call_misses(self): - """Test db call for retrieving miss data.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = Connection() # db_response is [] - assembly_id = "GRCh38" - position = (10, 20, None, None, None, None) - chromosome = 1 - reference = "A" - alternate = ("DUP", None) - result_miss = await fetch_filtered_dataset(pool, assembly_id, position, chromosome, reference, alternate, None, None, True) - self.assertEqual(result_miss, []) - - async def test_fetch_filtered_dataset_call_exception(self): - """Test db call of retrieving main data with exception.""" - assembly_id = "GRCh38" - position = (10, 20, None, None, None, None) - chromosome = 1 - reference = "A" - alternate = ("DUP", None) - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = ConnectionException() - with self.assertRaises(aiohttp.web_exceptions.HTTPInternalServerError): - await fetch_filtered_dataset(pool, assembly_id, position, chromosome, reference, alternate, None, None, False) - def test_handle_wildcard(self): """Test PostgreSQL wildcard handling.""" sequence1 = "ATCG" @@ -333,4 +192,4 @@ def test_handle_wildcard(self): if __name__ == "__main__": - asynctest.main() + unittest.main() diff --git a/tests/test_db_load.py b/tests/test_db_load.py index 960bda29..c6c4bf9a 100644 --- a/tests/test_db_load.py +++ b/tests/test_db_load.py @@ -1,378 +1,366 @@ -import unittest -import asynctest -import asyncio -from testfixtures import TempDirectory -from beacon_api.utils.db_load import BeaconDB - - -class Variant: - """Variant Class. - - Mock this for Variant calculations. - """ - - def __init__(self, ALT, REF, INF, call_rate, var_type, num_called, is_sv=False): - """Initialize class.""" - self.INFO = INF - self.ALT = ALT - self.REF = REF - self.call_rate = call_rate - self.var_type = var_type - self.num_called = num_called - self.is_sv = is_sv - - -class INFO: - """INFO CLass. - - Mock this for storing VCF info. - """ - - def __init__(self, AC, VT, AN, AF, SVTYPE=None): - """Initialize class.""" - self.AC = AC - self.VT = VT - self.AN = AN - self.AF = AF - self.SVTYPE = SVTYPE - - def get(self, key): - """Inside `__getitem__` method.""" - return getattr(self, key) - - -class Transaction: - """Class Transaction. - - Mock this from asyncpg. - """ - - def __init__(self, *args, **kwargs): - """Initialize class.""" - pass - - async def __aenter__(self): - """Initialize class.""" - pass - - async def __aexit__(self, *args): - """Initialize class.""" - pass - - -class Statement(Transaction): - """Class Transaction. - - Mock this from asyncpg. - """ - - def __init__(self, query, accessData): - """Initialize class.""" - self.accessData = accessData - pass - - async def fetch(self, *args, **kwargs): - """Mimic fetch.""" - if self.accessData: - return self.accessData - else: - return [] - - -class Connection: - """Class Connection. - - Mock this from asyncpg. - """ - - def __init__(self, accessData=None): - """Initialize class.""" - self.accessData = accessData - pass - - async def fetch(self, *args, **kwargs): - """Mimic fetch.""" - return [{"table_name": "DATATSET1"}, {"table_name": "DATATSET2"}] - - async def execute(self, query, *args): - """Mimic execute.""" - return [] - - async def close(self): - """Mimic close.""" - pass - - async def __aenter__(self): - """Initialize class.""" - pass - - async def __aexit__(self, *args): - """Initialize class.""" - pass - - @asyncio.coroutine - def prepare(self, query): - """Mimic prepare.""" - return Statement(query, self.accessData) - - def transaction(self, *args, **kwargs): - """Mimic transaction.""" - return Transaction(*args, **kwargs) - - -class ConnectionException: - """Class Connection with Exception. - - Mock this from asyncpg. - """ - - def __init__(self): - """Initialize class.""" - pass - - def transaction(self, *args, **kwargs): - """Mimic transaction.""" - return Transaction(*args, **kwargs) - - async def execute(self, query, *args): - """Mimic execute.""" - return Exception - - @asyncio.coroutine - def prepare(self, query): - """Mimic prepare.""" - return Exception - - -class DatabaseTestCase(asynctest.TestCase): - """Test database operations.""" - - def setUp(self): - """Initialise BeaconDB object.""" - self._db = BeaconDB() - self._dir = TempDirectory() - self.data = """##fileformat=VCFv4.0 - ##fileDate=20090805 - ##source=myImputationProgramV3.1 - ##reference=1000GenomesPilot-NCBI36 - ##phasing=partial - ##INFO= - ##INFO= - ##INFO= - ##INFO= - ##INFO= - ##INFO= - ##INFO= - ##INFO= - ##FILTER= - ##FILTER= - ##FORMAT= - ##FORMAT= - ##FORMAT= - ##FORMAT= - ##ALT= - ##ALT= - #CHROM POS ID REF ALT QUAL FILTER INFO FORMAT NA00001 NA00002 NA00003 - 19 111 . A C 9.6 . . GT:HQ 0|0:10,10 0|0:10,10 0/1:3,3 - 19 112 . A G 10 . . GT:HQ 0|0:10,10 0|0:10,10 0/1:3,3 - 20 14370 rs6054257 G A 29 PASS NS=3;DP=14;AF=0.5;DB;H2 GT:GQ:DP:HQ 0|0:48:1:51,51 1|0:48:8:51,51 1/1:43:5:.,. - chrM 15011 . T C . PASS . GT:GQ:DP:RO:QR:AO:QA:GL 1:160:970:0:0:968:31792:-2860.58,0 1:160:970:0:0:968:31792:-2860.58,0""" - self.datafile = self._dir.write("data.csv", self.data.encode("utf-8")) - - def tearDown(self): - """Close database connection after tests.""" - self._dir.cleanup_all() - - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_rchop(self, db_mock): - """Test rchop for SVTYPE.""" - db_mock.return_value = Connection() - await self._db.connection() - result = self._db._rchop("INS:ME:LINE1", ":LINE1") - self.assertEqual("INS:ME", result) - result_no_ending = self._db._rchop("INS", ":LINE1") - self.assertEqual("INS", result_no_ending) - - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_handle_type(self, db_mock): - """Test handle type.""" - db_mock.return_value = Connection() - await self._db.connection() - result = self._db._handle_type(1, int) - self.assertEqual([1], result) - result_tuple = self._db._handle_type((0.1, 0.2), float) - self.assertEqual([0.1, 0.2], result_tuple) - - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_bnd_parts(self, db_mock): - """Test breakend parsing parts.""" - db_mock.return_value = Connection() - await self._db.connection() - result = self._db._bnd_parts("[CHR17:31356925[N", "126_2") - self.assertEqual(("chr17", 31356925, True, True, "N", True, "126_2"), result) - - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg") - async def test_connection(self, db_mock): - """Test database URL fetching.""" - await self._db.connection() - db_mock.connect.assert_called() - - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_check_tables(self, db_mock): - """Test checking tables.""" - db_mock.return_value = Connection() - await self._db.connection() - db_mock.assert_called() - result = await self._db.check_tables(["DATATSET1", "DATATSET2"]) - # No Missing tables - self.assertEqual(result, []) - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_create_tables(self, db_mock, mock_log): - """Test creating tables.""" - sql = """CREATE TABLE IF NOT EXISTS beacon_data_table ( - id SERIAL, - dataset_id VARCHAR(200), - PRIMARY KEY (id));""" - db_mock.return_value = Connection() - await self._db.connection() - db_mock.assert_called() - sql_file = self._dir.write("sql.init", sql.encode("utf-8")) - await self._db.create_tables(sql_file) - # Should assert logs - mock_log.info.assert_called_with("Tables have been created") - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_create_tables_exception(self, db_mock, mock_log): - """Test creating tables exception.""" - db_mock.return_value = ConnectionException() - await self._db.connection() - await self._db.create_tables("sql.init") - log = "AN ERROR OCCURRED WHILE ATTEMPTING TO CREATE TABLES -> [Errno 2] No such file or directory: 'sql.init'" - mock_log.error.assert_called_with(log) - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - @asynctest.mock.patch("beacon_api.utils.db_load.VCF") - async def test_load_metadata(self, mock_vcf, db_mock, mock_log): - """Test load metadata.""" - metadata = """{"name": "ALL.chrMT.phase3_callmom-v0_4.20130502.genotypes.vcf", - "datasetId": "urn:hg:exampleid", - "description": "Mitochondrial genome from the 1000 Genomes project", - "assemblyId": "GRCh38", - "createDateTime": "2013-05-02 12:00:00", - "updateDateTime": "2013-05-02 12:00:00", - "version": "v0.4", - "externalUrl": "smth", - "accessType": "PUBLIC"}""" - db_mock.return_value = Connection() - await self._db.connection() - db_mock.assert_called() - metafile = self._dir.write("data.json", metadata.encode("utf-8")) - vcf = asynctest.mock.MagicMock(name="samples") - vcf.samples.return_value = [1, 2, 3] - await self._db.load_metadata(vcf, metafile, self.datafile) - # Should assert logs - mock_log.info.mock_calls = [f"Parsing metadata from {metafile}", "Metadata has been parsed"] - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_load_metadata_exception(self, db_mock, mock_log): - """Test load metadata error.""" - db_mock.return_value = ConnectionException() - await self._db.connection() - vcf = asynctest.mock.MagicMock(name="samples") - vcf.samples.return_value = [1, 2, 3] - await self._db.load_metadata(vcf, "meta.are", "datafile") - log = "AN ERROR OCCURRED WHILE ATTEMPTING TO PARSE METADATA -> [Errno 2] No such file or directory: 'meta.are'" - mock_log.error.assert_called_with(log) - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_load_datafile(self, db_mock, mock_log): - """Test load_datafile.""" - db_mock.return_value = Connection() - vcf = asynctest.mock.MagicMock(name="samples") - vcf.return_value = [{"record": 1}, {"record": 2}, {"records": 3}] - vcf.samples.return_value = [{"record": 1}, {"record": 2}, {"records": 3}] - await self._db.connection() - db_mock.assert_called() - await self._db.load_datafile(vcf, self.datafile, "DATASET1") - # Should assert logs - mock_log.info.mock_calls = [f"Read data from {self.datafile}", f"{self.datafile} has been processed"] - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_insert_variants(self, db_mock, mock_log): - """Test insert variants.""" - db_mock.return_value = Connection() - await self._db.connection() - db_mock.assert_called() - await self._db.insert_variants("DATASET1", ["C"], 1) - # Should assert logs - mock_log.info.mock_calls = ["Received 1 variants for insertion to DATASET1", "Insert variants into the database"] - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_close(self, db_mock, mock_log): - """Test database URL close.""" - db_mock.return_value = Connection() - await self._db.connection() - await self._db.close() - mock_log.info.mock_calls = ["Mark the database connection to be closed", "The database connection has been closed"] - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_close_error(self, db_mock, mock_log): - """Test database URL close error.""" - db_mock.return_value = ConnectionException() - await self._db.connection() - await self._db.close() - log = "AN ERROR OCCURRED WHILE ATTEMPTING TO CLOSE DATABASE CONNECTION -> 'ConnectionException' object has no attribute 'close'" - mock_log.error.assert_called_with(log) - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_unpack(self, db_mock, mock_log): - """Test database URL fetching.""" - db_mock.return_value = Connection() - await self._db.connection() - inf1 = INFO((1), "i", 3, None) - variant_1 = Variant(["C"], "T", inf1, 0.7, "indel", 3) - result = self._db._unpack(variant_1) - self.assertEqual(([0.3333333333333333], [1], ["SNP"], ["C"], 3, []), result) - inf2 = INFO(1, "M", 3, None) - variant_2 = Variant(["AT", "A"], "ATA", inf2, 0.7, "mnp", 3) - result = self._db._unpack(variant_2) - self.assertEqual(([0.3333333333333333], [1], ["DEL", "DEL"], ["AT", "A"], 3, []), result) - inf3 = INFO((1), "S", 3, 0.5) - variant_3 = Variant(["TC"], "T", inf3, 0.7, "snp", 3) - result = self._db._unpack(variant_3) - self.assertEqual(([0.5], [1], ["INS"], ["TC"], 3, []), result) - inf4 = INFO((1), "", 3, None, "BND") - variant_4 = Variant(["TC"], "T", inf4, 0.7, "snp", 3) - result = self._db._unpack(variant_4) - self.assertEqual(([0.3333333333333333], [1], ["SNP"], ["TC"], 3, []), result) - inf5 = INFO((1), "S", 3, None, "") - variant_5 = Variant(["TC"], "T", inf5, 0.7, "ins", 3) - result5 = self._db._unpack(variant_5) - self.assertEqual(([0.3333333333333333], [1], ["INS"], ["TC"], 3, []), result5) - - @asynctest.mock.patch("beacon_api.utils.db_load.LOG") - @asynctest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") - async def test_chunks(self, db_mock, mock_log): - """Test database URL fetching.""" - db_mock.return_value = Connection() - await self._db.connection() - variant = [(1, 2), (2, 3)] - result = self._db._chunks(variant, 1) - lines = [] - for i in result: - lines.append(list(i)) - self.assertEqual([[(1, 2)], [(2, 3)]], lines) - - -if __name__ == "__main__": - unittest.main() +import unittest +from testfixtures import TempDirectory +from beacon_api.utils.db_load import BeaconDB + + +class Variant: + """Variant Class. + + Mock this for Variant calculations. + """ + + def __init__(self, ALT, REF, INF, call_rate, var_type, num_called, is_sv=False): + """Initialize class.""" + self.INFO = INF + self.ALT = ALT + self.REF = REF + self.call_rate = call_rate + self.var_type = var_type + self.num_called = num_called + self.is_sv = is_sv + + +class INFO: + """INFO CLass. + + Mock this for storing VCF info. + """ + + def __init__(self, AC, VT, AN, AF, SVTYPE=None): + """Initialize class.""" + self.AC = AC + self.VT = VT + self.AN = AN + self.AF = AF + self.SVTYPE = SVTYPE + + def get(self, key): + """Inside `__getitem__` method.""" + return getattr(self, key) + + +class Transaction: + """Class Transaction. + + Mock this from asyncpg. + """ + + def __init__(self, *args, **kwargs): + """Initialize class.""" + pass + + async def __aenter__(self): + """Initialize class.""" + pass + + async def __aexit__(self, *args): + """Initialize class.""" + pass + + +class Statement(Transaction): + """Class Transaction. + + Mock this from asyncpg. + """ + + def __init__(self, query, accessData): + """Initialize class.""" + self.accessData = accessData + pass + + async def fetch(self, *args, **kwargs): + """Mimic fetch.""" + if self.accessData: + return self.accessData + else: + return [] + + +class Connection: + """Class Connection. + + Mock this from asyncpg. + """ + + def __init__(self, accessData=None): + """Initialize class.""" + self.accessData = accessData + pass + + async def fetch(self, *args, **kwargs): + """Mimic fetch.""" + return [{"table_name": "DATATSET1"}, {"table_name": "DATATSET2"}] + + async def execute(self, query, *args): + """Mimic execute.""" + return [] + + async def close(self): + """Mimic close.""" + pass + + async def __aenter__(self): + """Initialize class.""" + pass + + async def __aexit__(self, exc_type, exc, tb): + """Initialize class.""" + pass + + async def prepare(self, query): + """Mimic prepare.""" + return Statement(query, self.accessData) + + async def transaction(self, *args, **kwargs): + """Mimic transaction.""" + return Transaction(*args, **kwargs) + + +class ConnectionException: + """Class Connection with Exception. + + Mock this from asyncpg. + """ + + def __init__(self): + """Initialize class.""" + pass + + async def transaction(self, *args, **kwargs): + """Mimic transaction.""" + return Transaction(*args, **kwargs) + + async def execute(self, query, *args): + """Mimic execute.""" + return Exception + + async def prepare(self, query): + """Mimic prepare.""" + return Exception + + +class DatabaseTestCase(unittest.IsolatedAsyncioTestCase): + """Test database operations.""" + + def setUp(self): + """Initialise BeaconDB object.""" + self._db = BeaconDB() + self._dir = TempDirectory() + self.data = """##fileformat=VCFv4.0 + ##fileDate=20090805 + ##source=myImputationProgramV3.1 + ##reference=1000GenomesPilot-NCBI36 + ##phasing=partial + ##INFO= + ##INFO= + ##INFO= + ##INFO= + ##INFO= + ##INFO= + ##INFO= + ##INFO= + ##FILTER= + ##FILTER= + ##FORMAT= + ##FORMAT= + ##FORMAT= + ##FORMAT= + ##ALT= + ##ALT= + #CHROM POS ID REF ALT QUAL FILTER INFO FORMAT NA00001 NA00002 NA00003 + 19 111 . A C 9.6 . . GT:HQ 0|0:10,10 0|0:10,10 0/1:3,3 + 19 112 . A G 10 . . GT:HQ 0|0:10,10 0|0:10,10 0/1:3,3 + 20 14370 rs6054257 G A 29 PASS NS=3;DP=14;AF=0.5;DB;H2 GT:GQ:DP:HQ 0|0:48:1:51,51 1|0:48:8:51,51 1/1:43:5:.,. + chrM 15011 . T C . PASS . GT:GQ:DP:RO:QR:AO:QA:GL 1:160:970:0:0:968:31792:-2860.58,0 1:160:970:0:0:968:31792:-2860.58,0""" + self.datafile = self._dir.write("data.csv", self.data.encode("utf-8")) + + def tearDown(self): + """Close database connection after tests.""" + self._dir.cleanup_all() + + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_rchop(self, db_mock): + """Test rchop for SVTYPE.""" + db_mock.return_value = Connection() + await self._db.connection() + result = self._db._rchop("INS:ME:LINE1", ":LINE1") + self.assertEqual("INS:ME", result) + result_no_ending = self._db._rchop("INS", ":LINE1") + self.assertEqual("INS", result_no_ending) + + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_handle_type(self, db_mock): + """Test handle type.""" + db_mock.return_value = Connection() + await self._db.connection() + result = self._db._handle_type(1, int) + self.assertEqual([1], result) + result_tuple = self._db._handle_type((0.1, 0.2), float) + self.assertEqual([0.1, 0.2], result_tuple) + + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_bnd_parts(self, db_mock): + """Test breakend parsing parts.""" + db_mock.return_value = Connection() + await self._db.connection() + result = self._db._bnd_parts("[CHR17:31356925[N", "126_2") + self.assertEqual(("chr17", 31356925, True, True, "N", True, "126_2"), result) + + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg") + async def test_connection(self, db_mock): + """Test database URL fetching.""" + await self._db.connection() + db_mock.connect.assert_called() + + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_check_tables(self, db_mock): + """Test checking tables.""" + db_mock.return_value = Connection() + await self._db.connection() + db_mock.assert_called() + result = await self._db.check_tables(["DATATSET1", "DATATSET2"]) + # No Missing tables + self.assertEqual(result, []) + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_create_tables(self, db_mock, mock_log): + """Test creating tables.""" + sql = """CREATE TABLE IF NOT EXISTS beacon_data_table ( + id SERIAL, + dataset_id VARCHAR(200), + PRIMARY KEY (id));""" + db_mock.return_value = Connection() + await self._db.connection() + db_mock.assert_called() + sql_file = self._dir.write("sql.init", sql.encode("utf-8")) + await self._db.create_tables(sql_file) + # Should assert logs + mock_log.info.assert_called_with("Tables have been created") + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_create_tables_exception(self, db_mock, mock_log): + """Test creating tables exception.""" + db_mock.return_value = ConnectionException() + await self._db.connection() + await self._db.create_tables("sql.init") + log = "AN ERROR OCCURRED WHILE ATTEMPTING TO CREATE TABLES -> [Errno 2] No such file or directory: 'sql.init'" + mock_log.error.assert_called_with(log) + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + @unittest.mock.patch("beacon_api.utils.db_load.VCF") + async def test_load_metadata(self, mock_vcf, db_mock, mock_log): + """Test load metadata.""" + metadata = """{"name": "ALL.chrMT.phase3_callmom-v0_4.20130502.genotypes.vcf", + "datasetId": "urn:hg:exampleid", + "description": "Mitochondrial genome from the 1000 Genomes project", + "assemblyId": "GRCh38", + "createDateTime": "2013-05-02 12:00:00", + "updateDateTime": "2013-05-02 12:00:00", + "version": "v0.4", + "externalUrl": "smth", + "accessType": "PUBLIC"}""" + db_mock.return_value = Connection() + await self._db.connection() + db_mock.assert_called() + metafile = self._dir.write("data.json", metadata.encode("utf-8")) + vcf = unittest.mock.MagicMock(name="samples") + vcf.samples.return_value = [1, 2, 3] + await self._db.load_metadata(vcf, metafile, self.datafile) + # Should assert logs + mock_log.info.mock_calls = [f"Parsing metadata from {metafile}", "Metadata has been parsed"] + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_load_metadata_exception(self, db_mock, mock_log): + """Test load metadata error.""" + db_mock.return_value = ConnectionException() + await self._db.connection() + vcf = unittest.mock.MagicMock(name="samples") + vcf.samples.return_value = [1, 2, 3] + await self._db.load_metadata(vcf, "meta.are", "datafile") + log = "AN ERROR OCCURRED WHILE ATTEMPTING TO PARSE METADATA -> [Errno 2] No such file or directory: 'meta.are'" + mock_log.error.assert_called_with(log) + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_load_datafile(self, db_mock, mock_log): + """Test load_datafile.""" + db_mock.return_value = Connection() + vcf = unittest.mock.MagicMock(name="samples") + vcf.return_value = [{"record": 1}, {"record": 2}, {"records": 3}] + vcf.samples.return_value = [{"record": 1}, {"record": 2}, {"records": 3}] + await self._db.connection() + db_mock.assert_called() + await self._db.load_datafile(vcf, self.datafile, "DATASET1") + # Should assert logs + mock_log.info.mock_calls = [f"Read data from {self.datafile}", f"{self.datafile} has been processed"] + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_close(self, db_mock, mock_log): + """Test database URL close.""" + db_mock.return_value = Connection() + await self._db.connection() + await self._db.close() + mock_log.info.mock_calls = [ + "Mark the database connection to be closed", + "The database connection has been closed", + ] + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_close_error(self, db_mock, mock_log): + """Test database URL close error.""" + db_mock.return_value = ConnectionException() + await self._db.connection() + await self._db.close() + log = "AN ERROR OCCURRED WHILE ATTEMPTING TO CLOSE DATABASE CONNECTION -> 'ConnectionException' object has no attribute 'close'" + mock_log.error.assert_called_with(log) + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_unpack(self, db_mock, mock_log): + """Test database URL fetching.""" + db_mock.return_value = Connection() + await self._db.connection() + inf1 = INFO((1), "i", 3, None) + variant_1 = Variant(["C"], "T", inf1, 0.7, "indel", 3) + result = self._db._unpack(variant_1) + self.assertEqual(([0.3333333333333333], [1], ["SNP"], ["C"], 3, []), result) + inf2 = INFO(1, "M", 3, None) + variant_2 = Variant(["AT", "A"], "ATA", inf2, 0.7, "mnp", 3) + result = self._db._unpack(variant_2) + self.assertEqual(([0.3333333333333333], [1], ["DEL", "DEL"], ["AT", "A"], 3, []), result) + inf3 = INFO((1), "S", 3, 0.5) + variant_3 = Variant(["TC"], "T", inf3, 0.7, "snp", 3) + result = self._db._unpack(variant_3) + self.assertEqual(([0.5], [1], ["INS"], ["TC"], 3, []), result) + inf4 = INFO((1), "", 3, None, "BND") + variant_4 = Variant(["TC"], "T", inf4, 0.7, "snp", 3) + result = self._db._unpack(variant_4) + self.assertEqual(([0.3333333333333333], [1], ["SNP"], ["TC"], 3, []), result) + inf5 = INFO((1), "S", 3, None, "") + variant_5 = Variant(["TC"], "T", inf5, 0.7, "ins", 3) + result5 = self._db._unpack(variant_5) + self.assertEqual(([0.3333333333333333], [1], ["INS"], ["TC"], 3, []), result5) + + @unittest.mock.patch("beacon_api.utils.db_load.LOG") + @unittest.mock.patch("beacon_api.utils.db_load.asyncpg.connect") + async def test_chunks(self, db_mock, mock_log): + """Test database URL fetching.""" + db_mock.return_value = Connection() + await self._db.connection() + variant = [(1, 2), (2, 3)] + result = self._db._chunks(variant, 1) + lines = [] + for i in result: + lines.append(list(i)) + self.assertEqual([[(1, 2)], [(2, 3)]], lines) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_mate_name.py b/tests/test_mate_name.py index d6fc6fd7..b71d16a9 100644 --- a/tests/test_mate_name.py +++ b/tests/test_mate_name.py @@ -1,21 +1,11 @@ -import asynctest -import aiohttp -from beacon_api.extensions.mate_name import find_fusion, fetch_fusion_dataset -from .test_db_load import Connection, ConnectionException +import unittest +from beacon_api.extensions.mate_name import find_fusion -class TestDataQueryFunctions(asynctest.TestCase): +class TestDataQueryFunctions(unittest.IsolatedAsyncioTestCase): """Test Data Query functions.""" - def setUp(self): - """Set up.""" - pass - - def tearDown(self): - """Close database connection after tests.""" - pass - - @asynctest.mock.patch("beacon_api.extensions.mate_name.fetch_fusion_dataset") + @unittest.mock.patch("beacon_api.extensions.mate_name.fetch_fusion_dataset") async def test_find_fusion(self, mock_filtered): """Test find datasets.""" mock_filtered.return_value = [] @@ -25,87 +15,6 @@ async def test_find_fusion(self, mock_filtered): result_miss = await find_fusion(None, "GRCh38", (), "Y", "T", "C", [], access_type, "MISS") self.assertEqual(result_miss, []) - async def test_fetch_fusion_dataset_call(self): - """Test db call for retrieving mate data.""" - pool = asynctest.CoroutineMock() - db_response = { - "referenceBases": "", - "alternateBases": "", - "variantType": "", - "referenceName": "Chr38", - "frequency": 0, - "callCount": 0, - "sampleCount": 0, - "variantCount": 0, - "start": 0, - "end": 0, - "accessType": "PUBLIC", - "datasetId": "test", - } - pool.acquire().__aenter__.return_value = Connection(accessData=[db_response]) - assembly_id = "GRCh38" - position = (10, 20, None, None, None, None) - chromosome = 1 - reference = "A" - result = await fetch_fusion_dataset(pool, assembly_id, position, chromosome, reference, None, None, None, False) - # for now it can return empty dataset - # in order to get a response we will have to mock it - # in Connection() class - expected = { - "referenceName": "Chr38", - "callCount": 0, - "sampleCount": 0, - "variantCount": 0, - "datasetId": "test", - "referenceBases": "", - "alternateBases": "", - "variantType": "", - "start": 0, - "end": 0, - "frequency": 0, - "info": {"accessType": "PUBLIC"}, - "datasetHandover": [ - { - "handoverType": {"id": "CUSTOM", "label": "Variants"}, - "description": "browse the variants matched by the query", - "url": "https://examplebrowser.org/dataset/test/browser/variant/Chr38-1--", - }, - { - "handoverType": {"id": "CUSTOM", "label": "Region"}, - "description": "browse data of the region matched by the query", - "url": "https://examplebrowser.org/dataset/test/browser/region/Chr38-1-1", - }, - { - "handoverType": {"id": "CUSTOM", "label": "Data"}, - "description": "retrieve information of the datasets", - "url": "https://examplebrowser.org/dataset/test/browser", - }, - ], - } - self.assertEqual(result, [expected]) - - async def test_fetch_fusion_dataset_call_miss(self): - """Test db call for retrieving mate miss data.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = Connection() - assembly_id = "GRCh38" - position = (10, 20, None, None, None, None) - chromosome = 1 - reference = "A" - result_miss = await fetch_fusion_dataset(pool, assembly_id, position, chromosome, reference, None, None, None, True) - self.assertEqual(result_miss, []) - - async def test_fetch_fusion_dataset_call_exception(self): - """Test db call for retrieving mate data with exception.""" - pool = asynctest.CoroutineMock() - pool.acquire().__aenter__.return_value = ConnectionException() - assembly_id = "GRCh38" - position = (10, 20, None, None, None, None) - chromosome = 1 - reference = "A" - with self.assertRaises(aiohttp.web_exceptions.HTTPInternalServerError): - await fetch_fusion_dataset(pool, assembly_id, position, chromosome, reference, None, None, None, False) - if __name__ == "__main__": - asynctest.main() + unittest.main() diff --git a/tests/test_response.py b/tests/test_response.py index b3bd659c..da828822 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,6 +1,6 @@ from beacon_api.api.info import beacon_info, ga4gh_info from beacon_api.api.query import query_request_handler -import asynctest +import unittest from beacon_api.schemas import load_schema from beacon_api.utils.validate_jwt import get_key from beacon_api.permissions.ga4gh import retrieve_user_data, get_jwk @@ -56,14 +56,14 @@ ] -class TestBasicFunctions(asynctest.TestCase): +class TestBasicFunctions(unittest.IsolatedAsyncioTestCase): """Test supporting functions.""" - @asynctest.mock.patch("beacon_api.api.info.fetch_dataset_metadata") + @unittest.mock.patch("beacon_api.api.info.fetch_dataset_metadata") async def test_beacon_info(self, db_metadata): """Test info metadata response.""" db_metadata.return_value = [mock_dataset_metadata] - pool = asynctest.CoroutineMock() + pool = unittest.mock.AsyncMock() result = await beacon_info("localhost", pool) # if it is none no error occurred self.assertEqual(jsonschema.validate(json.loads(json.dumps(result)), load_schema("info")), None) @@ -75,13 +75,13 @@ async def test_ga4gh_info(self): # if it is none no error occurred self.assertEqual(jsonschema.validate(json.loads(json.dumps(result)), load_schema("service-info")), None) - @asynctest.mock.patch("beacon_api.api.query.find_datasets") - @asynctest.mock.patch("beacon_api.api.query.fetch_datasets_access") + @unittest.mock.patch("beacon_api.api.query.find_datasets") + @unittest.mock.patch("beacon_api.api.query.fetch_datasets_access") async def test_beacon_query(self, fetch_req_datasets, data_find): """Test query data response.""" data_find.return_value = mock_data fetch_req_datasets.return_value = mock_controlled - pool = asynctest.CoroutineMock() + pool = unittest.mock.AsyncMock() request = { "assemblyId": "GRCh38", "referenceName": "MT", @@ -97,13 +97,13 @@ async def test_beacon_query(self, fetch_req_datasets, data_find): self.assertEqual(jsonschema.validate(json.loads(json.dumps(result)), load_schema("response")), None) data_find.assert_called() - @asynctest.mock.patch("beacon_api.api.query.find_fusion") - @asynctest.mock.patch("beacon_api.api.query.fetch_datasets_access") + @unittest.mock.patch("beacon_api.api.query.find_fusion") + @unittest.mock.patch("beacon_api.api.query.fetch_datasets_access") async def test_beacon_query_bnd(self, fetch_req_datasets, data_find): """Test query data response.""" data_find.return_value = mock_data fetch_req_datasets.return_value = mock_controlled - pool = asynctest.CoroutineMock() + pool = unittest.mock.AsyncMock() request = { "assemblyId": "GRCh38", "referenceName": "MT", @@ -171,13 +171,13 @@ async def test_get_jwk(self, m): self.assertTrue(isinstance(result, dict)) self.assertTrue(result["keys"][0]["alg"], "RSA256") - @asynctest.mock.patch("beacon_api.permissions.ga4gh.LOG") + @unittest.mock.patch("beacon_api.permissions.ga4gh.LOG") async def test_get_jwk_bad(self, mock_log): """Test get JWK exception log.""" await get_jwk("http://test.csc.fi/jwk") mock_log.error.assert_called_with("Could not retrieve JWK from http://test.csc.fi/jwk") - @asynctest.mock.patch("beacon_api.utils.validate_jwt.OAUTH2_CONFIG", return_value={"server": None}) + @unittest.mock.patch("beacon_api.utils.validate_jwt.OAUTH2_CONFIG", return_value={"server": None}) async def test_bad_get_key(self, oauth_none): """Test bad test_get_key.""" with self.assertRaises(aiohttp.web_exceptions.HTTPInternalServerError): From 12c9bcb34bb4e47e40c40518db5c087be5707b08 Mon Sep 17 00:00:00 2001 From: Stefan Negru Date: Fri, 31 Dec 2021 18:01:11 +0200 Subject: [PATCH 4/5] bump alpine to 3.15 and fix htslib dependency htslib 1.10 is required for current cyvcf2 we install cython before we can install cyvcf2 --- Dockerfile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index dd309e1f..0e4f4057 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,24 @@ -FROM python:3.8-alpine3.13 as BUILD +FROM python:3.8-alpine3.15 as BUILD RUN apk add --update \ && apk add --no-cache build-base curl-dev linux-headers bash git musl-dev\ - && apk add --no-cache libressl-dev libffi-dev autoconf bzip2-dev xz-dev\ + && apk add --no-cache openssl-dev libffi-dev autoconf bzip2-dev xz-dev\ && apk add --no-cache python3-dev rust cargo \ && rm -rf /var/cache/apk/* COPY requirements.txt /root/beacon/requirements.txt -COPY setup.py /root/beacon/setup.py -COPY beacon_api /root/beacon/beacon_api ENV CYTHONIZE=1 RUN pip install --upgrade pip && \ - pip install -r /root/beacon/requirements.txt && \ - pip install /root/beacon + pip install Cython==0.29.26 && \ + pip install -r /root/beacon/requirements.txt + +COPY setup.py /root/beacon/setup.py +COPY beacon_api /root/beacon/beacon_api +RUN pip install /root/beacon -FROM python:3.8-alpine3.13 +FROM python:3.8-alpine3.15 RUN apk add --no-cache --update bash From 42e257d049eff7238b86f11e5b485f2ba8f9d01f Mon Sep 17 00:00:00 2001 From: Stefan Negru Date: Fri, 31 Dec 2021 18:01:56 +0200 Subject: [PATCH 5/5] bump to postgres 13 in docker compose --- README.md | 4 ++-- deploy/docker-compose.yml | 4 ++-- deploy/test/docker-compose.yml | 4 ++-- docs/db.rst | 2 +- docs/instructions.rst | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0a9df73a..2f86b872 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Documentation: https://beacon-python.readthedocs.io `beacon-python` Web Server requires: * Python 3.8+; -* running DB [PostgreSQL Server](https://www.postgresql.org/) 9.6+ (recommended 11.6). +* running DB [PostgreSQL Server](https://www.postgresql.org/) 9.6+ (recommended 13). ```shell git clone https://github.com/CSCfi/beacon-python @@ -30,7 +30,7 @@ docker run -e POSTGRES_USER=beacon \ -e POSTGRES_PASSWORD=beacon \ -v "$PWD/data":/docker-entrypoint-initdb.d \ -e POSTGRES_DB=beacondb \ - -p 5432:5432 postgres:11.6 + -p 5432:5432 postgres:13 ``` #### Run beacon-python diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 0fa7e5e7..9ad00d80 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -3,7 +3,7 @@ version: '3.2' services: postgres: hostname: postgres - image: postgres:11.6 + image: postgres:13 environment: POSTGRES_USER: beacon POSTGRES_DB: beacondb @@ -20,7 +20,7 @@ services: environment: DATABASE_URL: postgres links: - - postgres:postgres + - postgres:postgres ports: - 5050:5050 restart: on-failure diff --git a/deploy/test/docker-compose.yml b/deploy/test/docker-compose.yml index 14affcfd..d12a1fb4 100644 --- a/deploy/test/docker-compose.yml +++ b/deploy/test/docker-compose.yml @@ -3,7 +3,7 @@ version: '3.2' services: postgres: hostname: postgres - image: postgres:11.6 + image: postgres:13 environment: POSTGRES_USER: beacon POSTGRES_DB: beacondb @@ -42,4 +42,4 @@ services: - 8000:8000 volumes: - ./mock_auth.py:/mock_auth.py - entrypoint: ["python", "/mock_auth.py", "0.0.0.0", "8000"] + entrypoint: [ "python", "/mock_auth.py", "0.0.0.0", "8000" ] diff --git a/docs/db.rst b/docs/db.rst index 1ce6cd5c..7f976dcc 100644 --- a/docs/db.rst +++ b/docs/db.rst @@ -3,7 +3,7 @@ Database ======== -We use a PostgreSQL database (recommended version 11.6) for working with beacon data. +We use a PostgreSQL database (recommended version 13) for working with beacon data. For more information on setting up the database consult :ref:`database-setup`. .. attention:: We recommend https://pgtune.leopard.in.ua/ for establishing PostgreSQL diff --git a/docs/instructions.rst b/docs/instructions.rst index 72a5e63a..54e5f8fa 100644 --- a/docs/instructions.rst +++ b/docs/instructions.rst @@ -4,7 +4,7 @@ Instructions .. note:: In order to run ``beacon-python`` Web Server requirements are as specified below: * Python 3.8+; - * running DB `PostgreSQL Server `_ 9.6+ (recommended 11.6). + * running DB `PostgreSQL Server `_ 9.6+ (recommended 13). .. _env-setup: @@ -165,7 +165,7 @@ Starting PostgreSQL using Docker: -e POSTGRES_PASSWORD=beacon \ -e POSTGRES_DB=beacondb \ -v "$PWD/data":/docker-entrypoint-initdb.d \ - -p 5432:5432 postgres:11.6 + -p 5432:5432 postgres:13 .. hint:: If one has their own database the ``beacon_init`` utility can be skipped, and make use of their own database by: