Skip to content

Commit

Permalink
add more coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
NickAkhmetov committed Jul 18, 2024
1 parent d3adf28 commit a06865c
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 24 deletions.
26 changes: 15 additions & 11 deletions src/portal_visualization/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,18 +220,21 @@ def get_vitessce_conf_cells_and_lifted_uuid(

metadata = derived_entity.get("metadata", {})

if metadata.get("files"):
if metadata.get(
"files"
): # pragma: no cover # We have separate tests for the builder logic
derived_entity["files"] = metadata.get("files", [])
vitessce_conf = self.get_vitessce_conf_cells_and_lifted_uuid(
derived_entity, marker=marker, wrap_error=wrap_error, parent=entity
).vitessce_conf
vis_lifted_uuid = derived_entity["uuid"]
else: # no files
error = f'Related image entity {derived_entity["uuid"]} \
is missing file information (no "files" key found in its metadata).'
error = (
f'Related image entity {derived_entity["uuid"]} '
+ 'is missing file information (no "files" key found in its metadata).'
)
current_app.logger.info(
f'Missing metadata error encountered in dataset \
{entity["uuid"]}: {error}'
f'Missing metadata error encountered in dataset {entity["uuid"]}: {error}'
)
vitessce_conf = _create_vitessce_error(error)
# If the current entity does not have files and was not determined to have a
Expand All @@ -240,7 +243,7 @@ def get_vitessce_conf_cells_and_lifted_uuid(
vitessce_conf = ConfCells(None, None)

# Otherwise, just try to visualize the data for the entity itself:
else:
else: # pragma: no cover # We have separate tests for the builder logic
try:
Builder = get_view_config_builder(entity, self._get_assaytype(), parent)
builder = Builder(entity, self.groups_token, self.assets_endpoint)
Expand All @@ -253,7 +256,9 @@ def get_vitessce_conf_cells_and_lifted_uuid(
)
vitessce_conf = _create_vitessce_error(str(e))

if epic_uuid:
if (
epic_uuid is not None and vitessce_conf.conf is not None
): # pragma: no cover # TODO
EPICBuilder = get_epic_builder(epic_uuid)
vitessce_conf = EPICBuilder(vitessce_conf).get_conf_cells()

Expand All @@ -262,7 +267,7 @@ def get_vitessce_conf_cells_and_lifted_uuid(
)

# Helper to create a function that fetches assaytype from the API with current headers
def _get_assaytype(self):
def _get_assaytype(self): # pragma: no cover
def get_assaytype(entity):
uuid = entity.get("uuid")

Expand Down Expand Up @@ -363,7 +368,7 @@ def get_publication_ancillary_json(self, entity):
try:
publication_resp = self._file_request(publication_json_path)
publication_json = json.loads(publication_resp)
except HTTPException:
except HTTPException: # pragma: no cover
current_app.logger.error(
f"Fetching publication ancillary json threw error: {traceback.format_exc()}"
)
Expand Down Expand Up @@ -592,8 +597,7 @@ def _create_vitessce_error(error):
{
"component": "description",
"props": {
"description": "Error while generating the Vitessce configuration: "
+ error,
"description": f"Error while generating the Vitessce configuration: {error}"
},
"x": 0,
"y": 0,
Expand Down
193 changes: 180 additions & 13 deletions test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
from flask import Flask
import pytest

from src.portal_visualization.client import ApiClient
from portal_visualization.builders.base_builders import ConfCells
from src.portal_visualization.client import ApiClient, _create_vitessce_error

mock_hit_source = {
"uuid": "ABC123",
"hubmap_id": "HMB123.XYZ",
"mapped_metadata": {"age_unit": ["eons"], "age_value": [42]},
"mapped_metadata": {"age_unit": ["eons"], "age_value": ["42"]},
}

flattened_hit_source = {
"age_unit": "eons",
"age_value": "42",
}

mock_es = {
Expand Down Expand Up @@ -48,10 +54,14 @@ class MockResponse:
def __init__(self):
self.status_code = 200
self.content = json.dumps(mock_es)
self.text = json.dumps(mock_es)

def raise_for_status(self):
pass

def json(self):
return mock_es

return MockResponse()


Expand Down Expand Up @@ -135,7 +145,7 @@ def test_get_all_dataset_uuids(app, mocker):

mock_es_more_than_10k = {
"hits": {
"total": {"value": 10000},
"total": {"value": 10001},
"hits": [{"_id": f"ABC{i}", "_source": mock_hit_source} for i in range(10000)],
}
}
Expand Down Expand Up @@ -165,29 +175,186 @@ def test_get_dataset_uuids_more_than_10k(app, mocker):
assert error_info.match("At least 10k datasets")


def test_get_entities(app, mocker):
@pytest.mark.parametrize("plural_lc_entity_type", ("datasets", "samples", "donors"))
def test_get_entities(app, mocker, plural_lc_entity_type):
mocker.patch("requests.post", side_effect=mock_es_post)
with app.app_context():
api_client = ApiClient()
entities = api_client.get_entities(plural_lc_entity_type)
assert json.dumps(entities, indent=2) == json.dumps(
[flattened_hit_source], indent=2
)
pass


def test_get_entity(app, mocker):
pass
def test_get_entities_more_than_10k(app, mocker):
mocker.patch("requests.post", side_effect=mock_es_post_more_than_10k)
with app.app_context():
api_client = ApiClient()
with pytest.raises(Exception) as error_info:
api_client.get_entities("datasets")
assert error_info.match("At least 10k datasets")


def test_get_latest_entity_uuid(app, mocker):
pass
@pytest.mark.parametrize("params", ({"uuid": "uuid"}, {"hbm_id": "hubmap_id"}))
def test_get_entity(app, mocker, params):
mocker.patch("requests.post", side_effect=mock_es_post)
with app.app_context():
api_client = ApiClient()
entity = api_client.get_entity(**params)
assert json.dumps(entity, indent=2) == json.dumps(mock_hit_source, indent=2)


def test_get_files(app, mocker):
pass
def test_get_entity_two_ids(app, mocker):
with app.app_context():
api_client = ApiClient()
with pytest.raises(Exception) as error_info:
api_client.get_entity(uuid="uuid", hbm_id="hubmap_id")
assert error_info.match("Only UUID or HBM ID should be provided")


def test_get_vitessce_conf_cells_and_lifted_uuid(app, mocker):
pass
def mock_get_revisions(path, **kwargs):

mock_revisions = [
{"uuid": "ABC123", "revision_number": 10},
{"uuid": "DEF456", "revision_number": 11},
]

class MockResponse:
def __init__(self):
self.status_code = 200
self.text = "Logger call requires this"
self.content = json.dumps(mock_revisions)

def json(self):
return mock_revisions

def raise_for_status(self):
pass

return MockResponse()


@pytest.mark.parametrize(
"params",
(
{"uuid": "uuid", "type": "dataset"},
{"uuid": "uuid", "type": "sample"},
{"uuid": "uuid", "type": "donor"},
),
)
def test_get_latest_entity_uuid(app, mocker, params):
mocker.patch("requests.get", side_effect=mock_get_revisions)
with app.app_context():
api_client = ApiClient(entity_api_endpoint="entity-api-url")
entity_uuid = api_client.get_latest_entity_uuid(**params)
assert entity_uuid == "DEF456"


def mock_files_response(path, **kwargs):

mock_file_response = {
"hits": {
"hits": [
{"_id": "1234", "_source": {"files": [{"rel_path": "abc.txt"}]}},
{"_id": "5678", "_source": {"files": [{"rel_path": "def.txt"}]}},
]
}
}

class MockResponse:
def __init__(self):
self.status_code = 200
self.text = "Logger call requires this"
self.content = json.dumps(mock_file_response)

def json(self):
return mock_file_response

def raise_for_status(self):
pass

return MockResponse()


def test_get_files(app, mocker):
mocker.patch("requests.post", side_effect=mock_files_response)
with app.app_context():
api_client = ApiClient()
files = api_client.get_files(["1234", "5678"])
assert files == {"1234": ["abc.txt"], "5678": ["def.txt"]}


related_entity_no_files_error = _create_vitessce_error(
"Related image entity ABC123 is missing file information "
+ '(no "files" key found in its metadata).'
)


@pytest.mark.parametrize(
"entity, patched_function, side_effect, expected_conf, expected_vis_lifted_uuid",
[
(
# No metadata in descendant
{"uuid": "12345"},
"requests.post",
mock_es_post,
related_entity_no_files_error,
None,
),
(
# No descendants, not marked as having a visualization, no files
{"uuid": "12345"},
"requests.post",
mock_es_post_no_hits,
ConfCells(None, None),
None,
),
# TODO? Add more test cases for happy scenarios
],
)
def test_get_vitessce_conf_cells_and_lifted_uuid(
app,
mocker,
entity,
patched_function,
side_effect,
expected_conf,
expected_vis_lifted_uuid,
):
mocker.patch(patched_function, side_effect=side_effect)
with app.app_context():
api_client = ApiClient(
groups_token="token",
elasticsearch_endpoint="http://example.com",
portal_index_path="/",
ubkg_endpoint="http://example.com",
entity_api_endpoint="http://example.com",
soft_assay_endpoint="http://example.com",
soft_assay_endpoint_path="/",
)
vitessce_conf = api_client.get_vitessce_conf_cells_and_lifted_uuid(entity)
assert vitessce_conf.vitessce_conf == expected_conf
assert vitessce_conf.vis_lifted_uuid == expected_vis_lifted_uuid


@pytest.mark.parametrize("groups_token", [None, "token"])
def test_get_publication_ancillary_json(app, mocker, groups_token):
mocker.patch("requests.post", side_effect=mock_es_post)
mocker.patch("requests.get", side_effect=mock_get_s3_json_file)
with app.app_context():
api_client = ApiClient(groups_token=groups_token)
result = api_client.get_publication_ancillary_json({"uuid": "ABC123"})
assert result.publication_json == mock_es
assert result.vis_lifted_uuid == "ABC123"

def test_get_publication_ancillary_json(app, mocker):
pass


def test_get_metadata_descriptions(app, mocker):
mocker.patch("requests.get", side_effect=mock_get_s3_json_file)
with app.app_context():
api_client = ApiClient()
metadata_descriptions = api_client.get_metadata_descriptions()
assert metadata_descriptions == mock_es
pass

0 comments on commit a06865c

Please sign in to comment.