Repository: Clinical-Genomics/patientMatcher

Issue:
HGNC symbol to Ensembl ID conversion might return more than 1 gene Ensembl ID
For instance SKI gene: https://grch37.rest.ensembl.org/xrefs/symbol/homo_sapiens/SKI?external_db=HGNC

At the moment only the second Ensembl ID is captured, but this might lead to less matching results


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d565b19..1def5e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,8 +3,13 @@
 ### Changed
 - Save Ensembl ids for demo patient data genes automatically
 - Mock the Ensembl REST API converting gene symbols to Ensembl IDs in tests
+- Changed the tests for the gene conversion Ensembl APIs to use mocks APIs
+- Do not download resources when testing the update resources command
+- Use the requests lib instead of urllib.request to interrogate the Ensembl APIs
+- Split cli tests into 4 dedicated files (test_add, test_remove, test_update, test_commands)
 ### Fixed
 - Syntax in the disclaimer
+- Validate the Ensembl gene ID against the Ensembl API when multiple IDs are returned by converting a HGNC symbol.
 
 ## [2.8] - 2021-09-17
 ### Added
diff --git a/patientMatcher/utils/ensembl_rest_client.py b/patientMatcher/utils/ensembl_rest_client.py
index 51836fd..8e0a9da 100644
--- a/patientMatcher/utils/ensembl_rest_client.py
+++ b/patientMatcher/utils/ensembl_rest_client.py
@@ -1,9 +1,7 @@
 # -*- coding: UTF-8 -*-
-import json
 import logging
-from urllib.error import HTTPError
-from urllib.parse import urlencode
-from urllib.request import Request, urlopen
+
+import requests
 
 LOG = logging.getLogger(__name__)
 
@@ -60,16 +58,18 @@ class EnsemblRestApiClient:
         """
         data = {}
         try:
-            request = Request(url, headers=HEADERS)
-            response = urlopen(request)
-            self.except_on_invalid_response(response)
-            content = response.read()
-            if content:
-                data = json.loads(content)
-        except HTTPError as e:
-            LOG.info("Request failed for url {0}: Error: {1}\n".format(url, e))
-            data = e
-        except ValueError as e:
-            LOG.info("Request failed for url {0}: Error: {1}\n".format(url, e))
-            data = e
+            response = requests.get(
+                url,
+                headers=HEADERS,
+            )
+            data = response.json()
+            if response.status_code != 200:
+                raise ValueError("The API did not return valid data")
+        except requests.exceptions.MissingSchema as ex:
+            LOG.error("Request failed for url {0}: Missing Schrma error: {1}\n".format(url, ex))
+            data = ex
+        except ValueError as ex:
+            LOG.error("Request failed for url {0}. Value Error: {1}\n".format(url, ex))
+            data = ex
+
         return data
diff --git a/patientMatcher/utils/gene.py b/patientMatcher/utils/gene.py
index 437d0ee..202b3cf 100644
--- a/patientMatcher/utils/gene.py
+++ b/patientMatcher/utils/gene.py
@@ -15,12 +15,12 @@ def entrez_to_symbol(entrez_id):
     client = ensembl_client.EnsemblRestApiClient()
     url = "".join([client.server, "/xrefs/name/human/", entrez_id, "?external_db=EntrezGene"])
     results = client.send_request(url)
-    for gene in results:  # result is an array. First element is enough
-        return gene["display_id"]
+    for entry in results:  # result is an array. First element is enough
+        return entry["display_id"]
 
 
 def symbol_to_ensembl(gene_symbol):
-    """Convert gene symbol to ensembl id
+    """Convert hgnc gene symbol to Ensembl id
 
     Accepts:
         gene_symbol(str) ex. LIMS2
@@ -29,11 +29,26 @@ def symbol_to_ensembl(gene_symbol):
         ensembl_id(str) ex. ENSG00000072163
     """
     client = ensembl_client.EnsemblRestApiClient()
-    url = "".join([client.server, "/xrefs/symbol/homo_sapiens/", gene_symbol, "?external_db=HGNC"])
+    # First collect all Ensembl IDs connected to a given symbol
+    url = f"{client.server}/xrefs/symbol/homo_sapiens/{gene_symbol}?external_db=HGNC"
+
     results = client.send_request(url)
-    for gene in results:  # result is an array. First element is enough
-        if gene["id"].startswith("ENSG"):  # it's the ensembl id
-            return gene["id"]
+    ensembl_ids = []
+    for entry in results:  # result is an array. First element is enough
+        if entry["id"].startswith("ENSG") is False:
+            continue
+        ensembl_ids.append(entry["id"])
+
+    if len(ensembl_ids) == 1:
+        return ensembl_ids[0]
+
+    # In case of multiple Ensembl IDs returned by the API, return only the one which has the right HGNC symbol
+    for ensembl_id in ensembl_ids:
+        url = f"{client.server}/xrefs/id/{ensembl_id}?all_levels=1;external_db=HGNC;content-type=application/json"
+        results = client.send_request(url)
+        for entry in results:
+            if entry.get("display_id") == gene_symbol:
+                return ensembl_id
 
 
 def ensembl_to_symbol(ensembl_id):


Test patch:
diff --git a/tests/cli/test_add.py b/tests/cli/test_add.py
new file mode 100644
index 0000000..2fc6c18
--- /dev/null
+++ b/tests/cli/test_add.py
@@ -0,0 +1,141 @@
+from patientMatcher.cli.commands import cli
+
+
+def test_cli_add_demo_data(mock_app, database, mock_symbol_2_ensembl, monkeypatch):
+    """Test the class that adds demo data"""
+
+    # GIVEN a mocked Ensembl REST API for conversion of gene symbols to Ensembl IDs
+    class MockResponse(object):
+        def __init__(self, url):
+            self.status_code = 200
+            self.gene_symbol = url.split("homo_sapiens/")[1].split("?")[0]
+
+        def json(self):
+            return [{"id": self.gene_symbol, "type": "gene"}]
+
+    def mock_get(url, headers):
+        return MockResponse(url)
+
+    runner = mock_app.test_cli_runner()
+
+    # make sure that "patients" collection is empty
+    assert database["patients"].find_one() is None
+
+    # run the load demo command without the -compute_phenotypes flag
+    result = runner.invoke(cli, ["add", "demodata"])
+    assert result.exit_code == 0
+
+    # check that demo patients are inserted into database
+    demo_patients = database["patients"].find()
+    assert len(list(demo_patients)) == 50
+
+    # check that genomic features contain genes described by HGNC gene symbols and Ensmbl IDs
+    assert demo_patients[0]["genomicFeatures"][0]["gene"]["id"]
+    assert demo_patients[0]["genomicFeatures"][0]["gene"]["_geneName"]
+
+    # check that one demo client has been created
+    assert database["clients"].find_one()
+
+
+def test_cli_add_client(mock_app, database, test_client):
+
+    # make sure that "clients" collection is empty
+    assert database["client"].find_one() is None
+
+    # test add a server using the app cli
+    runner = mock_app.test_cli_runner()
+    result = runner.invoke(
+        cli,
+        [
+            "add",
+            "client",
+            "-id",
+            test_client["_id"],
+            "-token",
+            test_client["auth_token"],
+            "-url",
+            test_client["base_url"],
+        ],
+    )
+
+    assert result.exit_code == 0
+    assert "Inserted client" in result.output
+
+    # check that the server was added to the "nodes" collection
+    assert database["clients"].find_one()
+
+    # Try adding the client again
+    result = runner.invoke(
+        cli,
+        [
+            "add",
+            "client",
+            "-id",
+            test_client["_id"],
+            "-token",
+            test_client["auth_token"],
+            "-url",
+            test_client["base_url"],
+        ],
+    )
+    assert result.exit_code == 0
+    # And you should get an abort message
+    assert "Aborted" in result.output
+    # And number of clients in database should stay the same
+    results = database["clients"].find()
+    assert len(list(results)) == 1
+
+
+def test_cli_add_node(mock_app, database, test_node):
+    # make sure that "nodes" collection is empty
+    assert database["nodes"].find_one() is None
+
+    # test add a server using the app cli
+    runner = mock_app.test_cli_runner()
+    result = runner.invoke(
+        cli,
+        [
+            "add",
+            "node",
+            "-id",
+            test_node["_id"],
+            "-label",
+            "This is a test node",
+            "-token",
+            test_node["auth_token"],
+            "-matching_url",
+            test_node["matching_url"],
+            "-accepted_content",
+            test_node["accepted_content"],
+        ],
+    )
+    assert result.exit_code == 0
+    assert "Inserted node" in result.output
+
+    # check that the server was added to the "nodes" collection
+    assert database["nodes"].find_one()
+
+    # Try adding the node again
+    result = runner.invoke(
+        cli,
+        [
+            "add",
+            "node",
+            "-id",
+            test_node["_id"],
+            "-label",
+            "This is a test node",
+            "-token",
+            test_node["auth_token"],
+            "-matching_url",
+            test_node["matching_url"],
+            "-accepted_content",
+            test_node["accepted_content"],
+        ],
+    )
+    assert result.exit_code == 0
+    # And you should get an abort message
+    assert "Aborted" in result.output
+    # And number of nodes in database should stay the same
+    results = database["nodes"].find()
+    assert len(list(results)) == 1
diff --git a/tests/cli/test_commands.py b/tests/cli/test_commands.py
index 656f6f3..610ede0 100644
--- a/tests/cli/test_commands.py
+++ b/tests/cli/test_commands.py
@@ -1,10 +1,10 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 import pymongo
-import responses
 from flask_mail import Message
 from patientMatcher.cli.commands import cli
 from patientMatcher.parse.patient import mme_patient
+from patientMatcher.utils.ensembl_rest_client import requests
 
 
 def test_appname(mock_app):
@@ -25,265 +25,3 @@ def test_sendemail(mock_app, mock_mail):
     assert mock_mail._send_was_called
     assert mock_mail._message
     assert "Mail correctly sent" in result.output
-
-
-def test_cli_add_node(mock_app, database, test_node):
-    # make sure that "nodes" collection is empty
-    assert database["nodes"].find_one() is None
-
-    # test add a server using the app cli
-    runner = mock_app.test_cli_runner()
-    result = runner.invoke(
-        cli,
-        [
-            "add",
-            "node",
-            "-id",
-            test_node["_id"],
-            "-label",
-            "This is a test node",
-            "-token",
-            test_node["auth_token"],
-            "-matching_url",
-            test_node["matching_url"],
-            "-accepted_content",
-            test_node["accepted_content"],
-        ],
-    )
-    assert result.exit_code == 0
-    assert "Inserted node" in result.output
-
-    # check that the server was added to the "nodes" collection
-    assert database["nodes"].find_one()
-
-    # Try adding the node again
-    result = runner.invoke(
-        cli,
-        [
-            "add",
-            "node",
-            "-id",
-            test_node["_id"],
-            "-label",
-            "This is a test node",
-            "-token",
-            test_node["auth_token"],
-            "-matching_url",
-            test_node["matching_url"],
-            "-accepted_content",
-            test_node["accepted_content"],
-        ],
-    )
-    assert result.exit_code == 0
-    # And you should get an abort message
-    assert "Aborted" in result.output
-    # And number of nodes in database should stay the same
-    results = database["nodes"].find()
-    assert len(list(results)) == 1
-
-
-def test_cli_add_client(mock_app, database, test_client):
-
-    # make sure that "clients" collection is empty
-    assert database["client"].find_one() is None
-
-    # test add a server using the app cli
-    runner = mock_app.test_cli_runner()
-    result = runner.invoke(
-        cli,
-        [
-            "add",
-            "client",
-            "-id",
-            test_client["_id"],
-            "-token",
-            test_client["auth_token"],
-            "-url",
-            test_client["base_url"],
-        ],
-    )
-
-    assert result.exit_code == 0
-    assert "Inserted client" in result.output
-
-    # check that the server was added to the "nodes" collection
-    assert database["clients"].find_one()
-
-    # Try adding the client again
-    result = runner.invoke(
-        cli,
-        [
-            "add",
-            "client",
-            "-id",
-            test_client["_id"],
-            "-token",
-            test_client["auth_token"],
-            "-url",
-            test_client["base_url"],
-        ],
-    )
-    assert result.exit_code == 0
-    # And you should get an abort message
-    assert "Aborted" in result.output
-    # And number of clients in database should stay the same
-    results = database["clients"].find()
-    assert len(list(results)) == 1
-
-
-def test_cli_remove_client(mock_app, database, test_client):
-
-    # Add a client to database
-    runner = mock_app.test_cli_runner()
-    result = runner.invoke(
-        cli,
-        [
-            "add",
-            "client",
-            "-id",
-            test_client["_id"],
-            "-token",
-            test_client["auth_token"],
-            "-url",
-            test_client["base_url"],
-        ],
-    )
-    assert result.exit_code == 0
-
-    # check that the server was added to the "nodes" collection
-    assert database["clients"].find_one()
-
-    # Use the cli to remove client
-    result = runner.invoke(cli, ["remove", "client", "-id", test_client["_id"]])
-
-    # check that command is executed withour errors
-    assert result.exit_code == 0
-
-    # and that client is gone from database
-    assert database["clients"].find_one() is None
-
-
-def test_cli_remove_node(mock_app, database, test_node):
-
-    # Add a node to database
-    runner = mock_app.test_cli_runner()
-    result = runner.invoke(
-        cli,
-        [
-            "add",
-            "node",
-            "-id",
-            test_node["_id"],
-            "-label",
-            "This is a test node",
-            "-token",
-            test_node["auth_token"],
-            "-matching_url",
-            test_node["matching_url"],
-            "-accepted_content",
-            test_node["accepted_content"],
-        ],
-    )
-    assert result.exit_code == 0
-
-    # check that the server was added to the "nodes" collection
-    assert database["nodes"].find_one()
-
-    # Use the cli to remove client
-    result = runner.invoke(cli, ["remove", "node", "-id", test_node["_id"]])
-
-    # check that command is executed withour errors
-    assert result.exit_code == 0
-
-    # and that node is gone from database
-    assert database["nodes"].find_one() is None
-
-
-def test_cli_update_resources(mock_app):
-
-    runner = mock_app.test_cli_runner()
-
-    # run resources update command with --test flag:
-    result = runner.invoke(cli, ["update", "resources"])
-    assert result.exit_code == 0
-
-
-@responses.activate
-def test_cli_add_demo_data(mock_app, database, mock_symbol_2_ensembl):
-
-    # GIVEN a mocked Ensembl REST API:
-    for hgnc_symbol, ensembl_id in mock_symbol_2_ensembl.items():
-        responses.add(
-            responses.GET,
-            f"https://grch37.rest.ensembl.org/xrefs/symbol/homo_sapiens/{hgnc_symbol}?external_db=HGNC",
-            json=[{"id": ensembl_id}],
-            status=200,
-        )
-
-    runner = mock_app.test_cli_runner()
-
-    # make sure that "patients" collection is empty
-    assert database["patients"].find_one() is None
-
-    # run the load demo command without the -compute_phenotypes flag
-    result = runner.invoke(cli, ["add", "demodata"])
-    assert result.exit_code == 0
-
-    # check that demo patients are inserted into database
-    demo_patients = database["patients"].find()
-    assert len(list(demo_patients)) == 50
-
-    # check that genomic features contain genes described by HGNC gene symbols and Ensmbl IDs
-    assert demo_patients[0]["genomicFeatures"][0]["gene"]["id"]
-    assert demo_patients[0]["genomicFeatures"][0]["gene"]["_geneName"]
-
-    # check that one demo client has been created
-    assert database["clients"].find_one()
-
-
-def test_cli_remove_patient(mock_app, database, gpx4_patients, match_objs):
-
-    runner = mock_app.test_cli_runner()
-
-    # add a test patient to database
-    test_patient = mme_patient(gpx4_patients[0], True)  # True --> convert gene symbols to ensembl
-    inserted_id = mock_app.db["patients"].insert_one(test_patient).inserted_id
-    assert inserted_id == gpx4_patients[0]["id"]
-
-    # there is now 1 patient in database
-    assert database["patients"].find_one()
-
-    # test that without a valid id or label no patient is removed
-    result = runner.invoke(cli, ["remove", "patient", "-id", "", "-label", ""])
-    assert "Error" in result.output
-
-    # Add mock patient matches objects to database
-    database["matches"].insert_many(match_objs)
-    # There should be 2 matches in database for this patient:
-    results = database["matches"].find({"data.patient.id": inserted_id})
-    assert len(list(results)) == 2
-
-    # involke cli command to remove the patient by id and label
-    result = runner.invoke(
-        cli, ["remove", "patient", "-id", inserted_id, "-label", "350_1-test", "-leave_matches"]
-    )
-    assert result.exit_code == 0
-
-    # check that the patient was removed from database
-    assert database["patients"].find_one() is None
-
-    # But matches are still there
-    results = database["matches"].find({"data.patient.id": inserted_id})
-    assert len(list(results)) == 2
-
-    # Run remove patient command with option to remove matches but without patient ID
-    result = runner.invoke(cli, ["remove", "patient", "-label", "350_1-test", "-remove_matches"])
-    # And make sure that it doesn't work
-    assert "Please provide patient ID and not label to remove all its matches." in result.output
-
-    # Test now the proper command to remove patient matches:
-    result = runner.invoke(cli, ["remove", "patient", "-id", inserted_id, "-remove_matches"])
-    assert result.exit_code == 0
-
-    # And make sure that patient removal removed its matchings
-    assert database["matches"].find_one({"data.patient.id": inserted_id}) is None
diff --git a/tests/cli/test_remove.py b/tests/cli/test_remove.py
new file mode 100644
index 0000000..8205949
--- /dev/null
+++ b/tests/cli/test_remove.py
@@ -0,0 +1,118 @@
+from patientMatcher.cli.commands import cli
+from patientMatcher.parse.patient import mme_patient
+
+
+def test_cli_remove_client(mock_app, database, test_client):
+
+    # Add a client to database
+    runner = mock_app.test_cli_runner()
+    result = runner.invoke(
+        cli,
+        [
+            "add",
+            "client",
+            "-id",
+            test_client["_id"],
+            "-token",
+            test_client["auth_token"],
+            "-url",
+            test_client["base_url"],
+        ],
+    )
+    assert result.exit_code == 0
+
+    # check that the server was added to the "nodes" collection
+    assert database["clients"].find_one()
+
+    # Use the cli to remove client
+    result = runner.invoke(cli, ["remove", "client", "-id", test_client["_id"]])
+
+    # check that command is executed withour errors
+    assert result.exit_code == 0
+
+    # and that client is gone from database
+    assert database["clients"].find_one() is None
+
+
+def test_cli_remove_node(mock_app, database, test_node):
+
+    # Add a node to database
+    runner = mock_app.test_cli_runner()
+    result = runner.invoke(
+        cli,
+        [
+            "add",
+            "node",
+            "-id",
+            test_node["_id"],
+            "-label",
+            "This is a test node",
+            "-token",
+            test_node["auth_token"],
+            "-matching_url",
+            test_node["matching_url"],
+            "-accepted_content",
+            test_node["accepted_content"],
+        ],
+    )
+    assert result.exit_code == 0
+
+    # check that the server was added to the "nodes" collection
+    assert database["nodes"].find_one()
+
+    # Use the cli to remove client
+    result = runner.invoke(cli, ["remove", "node", "-id", test_node["_id"]])
+
+    # check that command is executed withour errors
+    assert result.exit_code == 0
+
+    # and that node is gone from database
+    assert database["nodes"].find_one() is None
+
+
+def test_cli_remove_patient(mock_app, database, gpx4_patients, match_objs):
+
+    runner = mock_app.test_cli_runner()
+
+    # add a test patient to database
+    test_patient = mme_patient(gpx4_patients[0], True)  # True --> convert gene symbols to ensembl
+    inserted_id = mock_app.db["patients"].insert_one(test_patient).inserted_id
+    assert inserted_id == gpx4_patients[0]["id"]
+
+    # there is now 1 patient in database
+    assert database["patients"].find_one()
+
+    # test that without a valid id or label no patient is removed
+    result = runner.invoke(cli, ["remove", "patient", "-id", "", "-label", ""])
+    assert "Error" in result.output
+
+    # Add mock patient matches objects to database
+    database["matches"].insert_many(match_objs)
+    # There should be 2 matches in database for this patient:
+    results = database["matches"].find({"data.patient.id": inserted_id})
+    assert len(list(results)) == 2
+
+    # involke cli command to remove the patient by id and label
+    result = runner.invoke(
+        cli, ["remove", "patient", "-id", inserted_id, "-label", "350_1-test", "-leave_matches"]
+    )
+    assert result.exit_code == 0
+
+    # check that the patient was removed from database
+    assert database["patients"].find_one() is None
+
+    # But matches are still there
+    results = database["matches"].find({"data.patient.id": inserted_id})
+    assert len(list(results)) == 2
+
+    # Run remove patient command with option to remove matches but without patient ID
+    result = runner.invoke(cli, ["remove", "patient", "-label", "350_1-test", "-remove_matches"])
+    # And make sure that it doesn't work
+    assert "Please provide patient ID and not label to remove all its matches." in result.output
+
+    # Test now the proper command to remove patient matches:
+    result = runner.invoke(cli, ["remove", "patient", "-id", inserted_id, "-remove_matches"])
+    assert result.exit_code == 0
+
+    # And make sure that patient removal removed its matchings
+    assert database["matches"].find_one({"data.patient.id": inserted_id}) is None
diff --git a/tests/cli/test_update.py b/tests/cli/test_update.py
new file mode 100644
index 0000000..37ea5d9
--- /dev/null
+++ b/tests/cli/test_update.py
@@ -0,0 +1,10 @@
+from patientMatcher.cli.commands import cli
+
+
+def test_cli_update_resources(mock_app):
+
+    runner = mock_app.test_cli_runner()
+
+    # run resources update command with --test flag:
+    result = runner.invoke(cli, ["update", "resources", "--test"])
+    assert result.exit_code == 0
diff --git a/tests/utils/test_ensembl_rest_api.py b/tests/utils/test_ensembl_rest_api.py
index e51583d..4186fdd 100644
--- a/tests/utils/test_ensembl_rest_api.py
+++ b/tests/utils/test_ensembl_rest_api.py
@@ -1,9 +1,7 @@
 # -*- coding: UTF-8 -*-
 import pytest
-import tempfile
-from urllib.error import HTTPError
-from urllib.parse import urlencode
 from patientMatcher.utils import ensembl_rest_client as ensembl_api
+from requests.exceptions import MissingSchema
 
 
 def test_except_on_invalid_response():
@@ -50,11 +48,11 @@ def test_send_request_wrong_url():
     """Successful requests are tested by other tests in this file.
     This test will trigger errors instead.
     """
-    url = "fakeyurl"
+    not_an_url = "foo"
     client = ensembl_api.EnsemblRestApiClient()
-    data = client.send_request(url)
-    assert type(data) == ValueError
+    data = client.send_request(not_an_url)
+    assert type(data) == MissingSchema
 
-    url = "https://grch37.rest.ensembl.org/fakeyurl"
+    url = f"https://grch37.rest.ensembl.org/{not_an_url}"
     data = client.send_request(url)
-    assert type(data) == HTTPError
+    assert type(data) == ValueError
diff --git a/tests/utils/test_gene.py b/tests/utils/test_gene.py
index 6dcc19b..e611ff9 100644
--- a/tests/utils/test_gene.py
+++ b/tests/utils/test_gene.py
@@ -1,36 +1,132 @@
 # -*- coding: utf-8 -*-
-import responses
+from patientMatcher.utils.ensembl_rest_client import requests
 from patientMatcher.utils.gene import ensembl_to_symbol, entrez_to_symbol, symbol_to_ensembl
 
 
-def test_ensembl_to_symbol():
-    # Test converting ensembl ID to official gene symbol
+def test_entrez_to_symbol():
+    """Test the function converting entrez ID to gene symbol"""
 
-    ensembl_id = "ENSG00000103591"
-    symbol = ensembl_to_symbol(ensembl_id)
-    assert symbol == "AAGAB"
+    # GIVEN an entrez ID
+    entrez_id = "3735"
+    # THAT should return a symbol
+    symbol = "KARS"
+
+    # GIVEN a patched API response
+    class MockResponse(object):
+        def __init__(self):
+            self.status_code = 200
+
+        def json(self):
+            return {"display_id": symbol}
+
+    def mock_get(url, headers):
+        return MockResponse()
+
+    # The EnsemblRestApiClient should return the right Ensembl ID
+    assert entrez_to_symbol(entrez_id) == symbol
 
 
-@responses.activate
-def test_symbol_to_ensembl(mock_symbol_2_ensembl):
-    """Test function converting official gene symbol to ensembl ID using the Ensembl REST API"""
+def test_symbol_to_ensembl_one_ensembl_id(monkeypatch):
+    """
+    Test function converting official gene symbol to ensembl ID using the Ensembl REST API
+    Test case when the Ensembl API return only one Ensembl ID as result
+    """
 
+    # GIVEN a gene symbol
     hgnc_symbol = "AAGAB"
+    ensembl_id = "ENSG00000103591"
+
+    # GIVEN a patched API response
+    class MockResponse(object):
+        def __init__(self):
+            self.status_code = 200
+
+        def json(self):
+            return [{"id": ensembl_id, "type": "gene"}]
+
+    def mock_get(url, headers):
+        return MockResponse()
+
+    monkeypatch.setattr(requests, "get", mock_get)
+
+    # The EnsemblRestApiClient should return the right Ensembl ID
+    assert ensembl_id == symbol_to_ensembl(hgnc_symbol)
+
 
-    # GIVEN a mocked Ensembl REST API
-    responses.add(
-        responses.GET,
-        f"https://grch37.rest.ensembl.org/xrefs/symbol/homo_sapiens/{hgnc_symbol}?external_db=HGNC",
-        json=[{"id": "ENSG00000103591"}],
-        status=200,
-    )
+def test_symbol_to_ensembl_multiple_ensembl_id(monkeypatch):
+    """Test function converting official gene symbol to ensembl ID using the Ensembl REST API
+    Test case when the Ensembl API return multiple Ensembl gene IDs for one HGNC gene symbol
+    """
+    # GIVEN a gene symbol
+    hgnc_symbol = "SKI"
+    rigth_ensembl_id = "ENSG00000157933"
+    wrong_ensembl_id = "ENSG00000054392"
 
+    # GIVEN a patched API response that returns data for 2 ensembl genes
+    class MockResponse(object):
+        def __init__(self, url):
+            self.status_code = 200
+            self.url = url
+
+        def json(self):
+            if hgnc_symbol in self.url:  # initial query, returns 2 Ensembl IDs
+                return [
+                    {"id": rigth_ensembl_id, "type": "gene"},
+                    {"id": wrong_ensembl_id, "type": "gene"},
+                ]
+            elif (
+                rigth_ensembl_id in self.url
+            ):  # second call to the API, returns the HGNC info for the right gene
+                return [
+                    {
+                        "primary_id": "HGNC:10896",
+                        "display_id": "SKI",
+                        "description": "SKI proto-oncogene",
+                        "dbname": "HGNC",
+                    }
+                ]
+            elif (
+                wrong_ensembl_id in self.url
+            ):  # second call to the API, returns the HGNC info for the wrong gene
+                return [
+                    {
+                        "primary_id": "HGNC:18270",
+                        "display_id": "HHAT",
+                        "description": "hedgehog acyltransferase",
+                        "dbname": "HGNC",
+                    }
+                ]
+
+    def mock_get(url, headers):
+        return MockResponse(url)
+
+    monkeypatch.setattr(requests, "get", mock_get)
+
+    # The EnsemblRestApiClient should return the right Ensembl ID
     ensembl_id = symbol_to_ensembl(hgnc_symbol)
-    assert ensembl_id == "ENSG00000103591"
+    assert ensembl_id == rigth_ensembl_id
 
 
-def test_entrez_to_symbol():
-    # Test converting entrez ID to gene symbol
-    entrez_id = "3735"
-    symbol = entrez_to_symbol(entrez_id)
-    assert symbol == "KARS"
+def test_ensembl_to_symbol(monkeypatch):
+    """Test converting ensembl ID to official gene symbol using the Ensembl APIs"""
+
+    # GIVEN an Ensembl ID
+    ensembl_id = "ENSG00000103591"
+    # THAT should return a certain symbl
+    symbol = "AAGAB"
+
+    # GIVEN a patched API response
+    class MockResponse(object):
+        def __init__(self):
+            self.status_code = 200
+
+        def json(self):
+            return {"display_name": symbol}
+
+    def mock_get(url, headers):
+        return MockResponse()
+
+    monkeypatch.setattr(requests, "get", mock_get)
+
+    # The EnsemblRestApiClient should return the right symbl
+    assert ensembl_to_symbol(ensembl_id) == symbol