From 21183bd3f54895c483e7b061de5d565317766ce3 Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 16 Jul 2024 20:45:55 -0700 Subject: [PATCH] coverage --- src/aind_slims_api/behavior_session.py | 12 +- src/aind_slims_api/core.py | 16 +- src/aind_slims_api/instrument.py | 4 +- src/aind_slims_api/mouse.py | 39 +- src/aind_slims_api/unit.py | 4 +- src/aind_slims_api/user.py | 6 +- ...etch_attachments_response.json_entity.json | 336 ++++++++++++++++++ tests/test_behavior_session.py | 45 +-- tests/test_core.py | 83 ++++- tests/test_instrument.py | 37 +- tests/test_mouse.py | 54 +-- tests/test_user.py | 60 +--- 12 files changed, 471 insertions(+), 225 deletions(-) create mode 100644 tests/resources/example_fetch_attachments_response.json_entity.json diff --git a/src/aind_slims_api/behavior_session.py b/src/aind_slims_api/behavior_session.py index f9d90d6..080cbf1 100644 --- a/src/aind_slims_api/behavior_session.py +++ b/src/aind_slims_api/behavior_session.py @@ -8,7 +8,7 @@ from pydantic import Field -from aind_slims_api.core import SlimsBaseModel, SlimsClient, SLIMSTABLES +from aind_slims_api.core import SlimsBaseModel, SlimsClient from aind_slims_api.mouse import SlimsMouseContent from aind_slims_api.instrument import SlimsInstrument from aind_slims_api.user import SlimsUser @@ -30,7 +30,11 @@ class SlimsBehaviorSessionContentEvent(SlimsBaseModel): pk: int | None = Field(default=None, alias="cnvn_pk") mouse_pk: int | None = Field( - default=None, alias="cnvn_fk_content" + default=None, + alias="cnvn_fk_content", + description=( + "The primary key of the mouse associated with this behavior session." + ), ) # used as reference to mouse notes: str | None = Field(default=None, alias="cnvn_cf_notes") task_stage: str | None = Field(default=None, alias="cnvn_cf_taskStage") @@ -47,8 +51,8 @@ class SlimsBehaviorSessionContentEvent(SlimsBaseModel): default=None, alias="cnvn_cf_softwareVersion") date: datetime | None = Field(default=None, alias="cnvn_cf_scheduledDate") cnvn_fk_contentEventType: int = 10 # pk of Behavior Session ContentEvent - _slims_table: SLIMSTABLES = "ContentEvent" - _base_fetch_filters: ClassVar[dict[str, int | str]] = { + _slims_table = "ContentEvent" + _base_fetch_filters: ClassVar[dict[str, str]] = { "cnvt_name": "Behavior Session", } diff --git a/src/aind_slims_api/core.py b/src/aind_slims_api/core.py index 180f21e..73f2458 100644 --- a/src/aind_slims_api/core.py +++ b/src/aind_slims_api/core.py @@ -97,8 +97,8 @@ class MyModel(SlimsBaseModel): pk: Optional[int] = None json_entity: Optional[dict] = None - _slims_table: SLIMSTABLES - _base_fetch_filters: ClassVar[dict[str, str | int]] = {} # use for fetch_models, fetch_model + _slims_table: ClassVar[SLIMSTABLES] + _base_fetch_filters: ClassVar[dict[str, str]] = {} # use for fetch_models, fetch_model @field_validator("*", mode="before") def _validate(cls, value, info: ValidationInfo): @@ -141,11 +141,11 @@ def _serialize(self, field, info): # TODO: Add Table - need Record.json_entity['tableName'] -class Attachment(SlimsBaseModel): +class SlimsAttachment(SlimsBaseModel): pk: int = Field(..., alias="attm_pk") name: str = Field(..., alias="attm_name") - _slims_table: SLIMSTABLES = "Attachment" + _slims_table = "Attachment" SlimsBaseModelTypeVar = TypeVar("SlimsBaseModelTypeVar", bound=SlimsBaseModel) @@ -281,7 +281,7 @@ def fetch_models( ] logger.debug("Resolved sort: %s", resolved_sort) response = self.fetch( - model._slims_table.default, # TODO: consider changing fetch method + model._slims_table, # TODO: consider changing fetch method *args, sort=resolved_sort, start=start, @@ -321,15 +321,15 @@ def fetch_model( def fetch_attachments( self, record: SlimsBaseModel, - ) -> list[Attachment]: + ) -> list[SlimsAttachment]: return self._validate_models( - Attachment, + SlimsAttachment, self.db.slims_api.get_entities( f"attachment/{record._slims_table}/{record.pk}" ) ) - def fetch_attachment_content(self, attachment: Attachment) -> Response: + def fetch_attachment_content(self, attachment: SlimsAttachment) -> Response: return self.db.slims_api.get(f"repo/{attachment.pk}") @lru_cache(maxsize=None) diff --git a/src/aind_slims_api/instrument.py b/src/aind_slims_api/instrument.py index 108b8f7..604c1cb 100644 --- a/src/aind_slims_api/instrument.py +++ b/src/aind_slims_api/instrument.py @@ -4,7 +4,7 @@ from pydantic import Field -from aind_slims_api.core import SlimsBaseModel, SLIMSTABLES +from aind_slims_api.core import SlimsBaseModel logger = logging.getLogger() @@ -25,7 +25,7 @@ class SlimsInstrument(SlimsBaseModel): description="The name of the instrument", ) pk: int = Field(..., alias="nstr_pk") - _slims_table: SLIMSTABLES = "Instrument" + _slims_table = "Instrument" # todo add more useful fields diff --git a/src/aind_slims_api/mouse.py b/src/aind_slims_api/mouse.py index 41828c9..18b9651 100644 --- a/src/aind_slims_api/mouse.py +++ b/src/aind_slims_api/mouse.py @@ -3,9 +3,9 @@ import logging from typing import Annotated, ClassVar -from pydantic import Field, BeforeValidator, ValidationError +from pydantic import Field, BeforeValidator -from aind_slims_api.core import SlimsBaseModel, SlimsClient, UnitSpec, SLIMSTABLES +from aind_slims_api.core import SlimsBaseModel, UnitSpec logger = logging.getLogger() @@ -30,8 +30,8 @@ class SlimsMouseContent(SlimsBaseModel): barcode: str = Field(..., alias="cntn_barCode") pk: int = Field(..., alias="cntn_pk") - _slims_table: SLIMSTABLES = "Content" - _base_fetch_filters: ClassVar[dict[str, int | str]] = { + _slims_table = "Content" + _base_fetch_filters: ClassVar[dict[str, str]] = { "cntp_name": "Mouse", } @@ -52,37 +52,6 @@ class SlimsMouseContent(SlimsBaseModel): # cntn_cf_parentBarcode: SlimsColumn -def fetch_mouse_content( - client: SlimsClient, - mouse_name: str, -) -> SlimsMouseContent | dict | None: - """Fetches mouse information for a mouse with labtracks id {mouse_name}""" - mice = client.fetch( - "Content", - cntp_name="Mouse", - cntn_barCode=mouse_name, - ) - - if len(mice) > 0: - mouse_details = mice[0] - if len(mice) > 1: - logger.warning( - f"Warning, Multiple mice in SLIMS with barcode " - f"{mouse_name}, using pk={mouse_details.cntn_pk.value}" - ) - else: - logger.warning("Warning, Mouse not in SLIMS") - return None - - try: - mouse = SlimsMouseContent.model_validate(mouse_details) - except ValidationError as e: - logger.error(f"SLIMS data validation failed, {repr(e)}") - return mouse_details.json_entity - - return mouse - - if __name__ == "__main__": from aind_slims_api import testmod diff --git a/src/aind_slims_api/unit.py b/src/aind_slims_api/unit.py index 20a8709..848de64 100644 --- a/src/aind_slims_api/unit.py +++ b/src/aind_slims_api/unit.py @@ -5,7 +5,7 @@ from pydantic import Field -from aind_slims_api.core import SlimsBaseModel, SLIMSTABLES +from aind_slims_api.core import SlimsBaseModel logger = logging.getLogger() @@ -17,4 +17,4 @@ class SlimsUnit(SlimsBaseModel): abbreviation: Optional[str] = Field("", alias="unit_abbreviation") pk: int = Field(..., alias="unit_pk") - _slims_table: SLIMSTABLES = "Unit" + _slims_table = "Unit" diff --git a/src/aind_slims_api/user.py b/src/aind_slims_api/user.py index bc94a31..1835db6 100644 --- a/src/aind_slims_api/user.py +++ b/src/aind_slims_api/user.py @@ -3,7 +3,7 @@ from pydantic import Field -from aind_slims_api.core import SlimsBaseModel, SLIMSTABLES +from aind_slims_api.core import SlimsBaseModel # TODO: Tighten this up once users are more commonly used @@ -12,7 +12,7 @@ class SlimsUser(SlimsBaseModel): >>> from aind_slims_api.core import SlimsClient >>> client = SlimsClient() - >>> user = client.fetch_model(SlimsUser, "LisaK") + >>> user = client.fetch_model(SlimsUser, username="LKim") """ username: str = Field(..., alias="user_userName") @@ -22,7 +22,7 @@ class SlimsUser(SlimsBaseModel): email: Optional[str] = Field("", alias="user_email") pk: int = Field(..., alias="user_pk") - _slims_table: SLIMSTABLES = "User" + _slims_table = "User" if __name__ == "__main__": diff --git a/tests/resources/example_fetch_attachments_response.json_entity.json b/tests/resources/example_fetch_attachments_response.json_entity.json new file mode 100644 index 0000000..2cd17d6 --- /dev/null +++ b/tests/resources/example_fetch_attachments_response.json_entity.json @@ -0,0 +1,336 @@ +[{ + "canDelete": true, + "canUpdate": true, + "columns": [ + { + "datatype": "STRING", + "editable": true, + "hidden": false, + "name": "attm_name", + "position": 0, + "title": "Name", + "value": "rig323_EPHYS1_OPTO_2024-02-12.json" + }, + { + "datatype": "STRING", + "editable": true, + "hidden": false, + "name": "attm_uniqueIdentifier", + "position": 1, + "title": "Unique identifier", + "value": "21387bbf-d1a5-4c26-86c1-2bce06f21c98" + }, + { + "datatype": "FOREIGN_KEY", + "displayField": "attp_name", + "displayValue": null, + "editable": false, + "foreignDisplayColumn": "attp_name", + "foreignTable": "AttachmentType", + "hidden": true, + "name": "attm_fk_attachmentType", + "position": 2, + "title": "Type", + "value": null + }, + { + "datatype": "FOREIGN_KEY", + "displayField": "user_userName", + "displayValue": null, + "editable": true, + "foreignDisplayColumn": "user_userName", + "foreignTable": "User", + "hidden": false, + "name": "attm_fk_user", + "position": 3, + "title": "User", + "value": null + }, + { + "datatype": "FOREIGN_KEY", + "displayField": "grps_groupName", + "displayValue": null, + "editable": true, + "foreignDisplayColumn": "grps_groupName", + "foreignTable": "Groups", + "hidden": false, + "name": "attm_fk_group", + "position": 4, + "title": "Group", + "value": null + }, + { + "datatype": "BOOLEAN", + "editable": true, + "hidden": false, + "name": "attm_mainReport", + "position": 5, + "title": "Main report", + "value": false + }, + { + "datatype": "INTEGER", + "editable": true, + "hidden": false, + "name": "attm_file_filesize", + "position": 6, + "title": "Size", + "value": 21125 + }, + { + "datatype": "DATE", + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "editable": true, + "hidden": false, + "name": "attm_file_date_created", + "position": 7, + "subType": "datetime", + "timeZone": "America/Los_Angeles", + "title": "Date", + "value": 1711642546857 + }, + { + "datatype": "BOOLEAN", + "editable": true, + "hidden": false, + "name": "attm_isDirectory", + "position": 8, + "title": "Directory", + "value": false + }, + { + "datatype": "INTEGER", + "editable": true, + "hidden": false, + "name": "attm_linkCount", + "position": 9, + "title": "# of links to this attachment", + "value": 1 + }, + { + "datatype": "BOOLEAN", + "editable": true, + "hidden": false, + "name": "attm_currentlyLinked", + "position": 10, + "title": "Currently linked", + "value": null + }, + { + "datatype": "STRING", + "editable": true, + "hidden": false, + "name": "attm_createdBy", + "position": 11, + "title": "Created by", + "value": "LKim" + }, + { + "datatype": "DATE", + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "editable": true, + "hidden": false, + "name": "attm_createdOn", + "position": 12, + "subType": "datetime", + "timeZone": "America/Los_Angeles", + "title": "Created on", + "value": 1711642546906 + }, + { + "datatype": "STRING", + "editable": true, + "hidden": false, + "name": "attm_modifiedBy", + "position": 13, + "title": "Modified by", + "value": "LKim" + }, + { + "datatype": "DATE", + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "editable": true, + "hidden": false, + "name": "attm_modifiedOn", + "position": 14, + "subType": "datetime", + "timeZone": "America/Los_Angeles", + "title": "Modified on", + "value": 1711642546906 + }, + { + "datatype": "STRING", + "editable": true, + "hidden": false, + "name": "attm_ecm3Url", + "position": 15, + "title": "ECM Server URI", + "value": null + }, + { + "datatype": "FOREIGN_KEY", + "displayField": "user_userName", + "displayValue": null, + "editable": true, + "foreignDisplayColumn": "user_userName", + "foreignTable": "User", + "hidden": false, + "name": "attm_fk_ecm3UploadUser", + "position": 16, + "title": "ECM Upload user", + "value": 7 + }, + { + "datatype": "ENUM", + "editable": true, + "hidden": false, + "name": "attm_ecm3UploadStatus", + "position": 17, + "title": "ECM Upload Status", + "value": null + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "grps_groupName", + "position": 18, + "title": "Name", + "value": null + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "attm_externalId", + "position": 19, + "title": "attm_externalId", + "value": null + }, + { + "datatype": "ENUM", + "editable": true, + "hidden": true, + "name": "attm_analysisRole", + "position": 20, + "title": "attm_analysisRole", + "value": null + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "attm_file_filename", + "position": 21, + "title": "File name", + "value": "rig323_EPHYS1_OPTO_2024-02-12.json" + }, + { + "datatype": "INTEGER", + "editable": true, + "hidden": true, + "name": "attm_pk", + "position": 22, + "title": "attm_pk", + "value": 21 + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "attp_name", + "position": 23, + "title": "Name", + "value": null + }, + { + "datatype": "BOOLEAN", + "editable": true, + "hidden": true, + "name": "attm_isRemote", + "position": 24, + "title": "Remote", + "value": false + }, + { + "datatype": "BOOLEAN", + "editable": true, + "hidden": true, + "name": "attm_isMachineData", + "position": 25, + "title": "Machine data", + "value": false + }, + { + "datatype": "ENUM", + "displayValue": "Not indexable", + "editable": true, + "hidden": true, + "name": "attm_indexingStatus", + "position": 26, + "title": "Indexing status", + "value": "NOT_INDEXABLE" + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "attm_ecm3Id", + "position": 27, + "title": "ECM ID", + "value": null + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "attm_hash", + "position": 28, + "title": "Hash", + "value": "1e1eb28f644f2c51c209371eec79bd21" + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "attm_path", + "position": 29, + "title": "Path", + "value": "ff/f7c/b0b6f9b42788ac937769afea8be/rig323_EPHYS1_OPTO_2024-02-12.json" + }, + { + "datatype": "STRING", + "editable": true, + "hidden": true, + "name": "user_userName", + "position": 30, + "title": "User name", + "value": null + } + ], + "links": [ + { + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Attachment/21", + "rel": "self" + }, + { + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/User/7", + "rel": "attm_fk_ecm3UploadUser" + }, + { + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/AttachmentLink?atln_fk_attachment=21", + "rel": "-atln_fk_attachment" + }, + { + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/attachment/Attachment/21", + "rel": "attachments" + }, + { + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/repo/21", + "rel": "contents" + } + ], + "pk": 21, + "tableName": "Attachment" + } +] \ No newline at end of file diff --git a/tests/test_behavior_session.py b/tests/test_behavior_session.py index 71644a3..7102f7b 100644 --- a/tests/test_behavior_session.py +++ b/tests/test_behavior_session.py @@ -16,7 +16,6 @@ from aind_slims_api.user import SlimsUser from aind_slims_api.instrument import SlimsInstrument from aind_slims_api.behavior_session import ( - fetch_behavior_session_content_events, write_behavior_session_content_events, SlimsBehaviorSessionContentEvent, ) @@ -104,52 +103,30 @@ def setUpClass(cls): def test_fetch_behavior_session_content_events_success(self, mock_fetch: MagicMock): """Test fetch_behavior_session_content_events when successful""" mock_fetch.return_value = self.example_response - validated, unvalidated = fetch_behavior_session_content_events( - self.example_client, self.example_mouse + validated = self.example_client.fetch_models( + SlimsBehaviorSessionContentEvent, + mouse_pk=self.example_mouse.pk, + sort="date", ) - ret_entities = [item.json_entity for item in validated] + [ - item["json_entity"] for item in unvalidated - ] self.assertEqual( [item.json_entity for item in self.example_response], - ret_entities, + [item.json_entity for item in validated], ) @patch("slims.slims.Slims.fetch") - def test_fetch_behavior_session_content_events_success_unvalidated( - self, mock_fetch: MagicMock - ): + def test_fetch_behavior_session_content_events_success_sort_list(self, mock_fetch: MagicMock): """Test fetch_behavior_session_content_events when successful""" mock_fetch.return_value = self.example_response - validated, unvalidated = fetch_behavior_session_content_events( - self.example_client, self.example_mouse_response[0].json_entity + validated = self.example_client.fetch_models( + SlimsBehaviorSessionContentEvent, + mouse_pk=self.example_mouse.pk, + sort=["date"], ) - ret_entities = [item.json_entity for item in validated] + [ - item["json_entity"] for item in unvalidated - ] self.assertEqual( [item.json_entity for item in self.example_response], - ret_entities, + [item.json_entity for item in validated], ) - @patch("slims.slims.Slims.fetch") - def test_fetch_behavior_session_content_events_failure_none( - self, mock_fetch: MagicMock - ): - """Test fetch_behavior_session_content_events when supplied with None""" - mock_fetch.return_value = self.example_response - with self.assertRaises(ValueError): - fetch_behavior_session_content_events(self.example_client, None) - - @patch("slims.slims.Slims.fetch") - def test_fetch_behavior_session_content_events_failure_bad_value( - self, mock_fetch: MagicMock - ): - """Test fetch_behavior_session_content_events when supplied with None""" - mock_fetch.return_value = self.example_response - with self.assertRaises(ValueError): - fetch_behavior_session_content_events(self.example_client, 1) - @patch("slims.slims.Slims.add") def test_write_behavior_session_content_events_success( self, diff --git a/tests/test_core.py b/tests/test_core.py index 6ac5923..1fe7fbd 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,12 +5,13 @@ import unittest from copy import deepcopy from pathlib import Path +from requests import Response from unittest.mock import MagicMock, patch from slims.criteria import conjunction, equals from slims.internal import Record, _SlimsApiException -from aind_slims_api.core import SlimsClient +from aind_slims_api.core import SlimsClient, SlimsAttachment from aind_slims_api.unit import SlimsUnit RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__))) / "resources" @@ -19,6 +20,12 @@ class TestSlimsClient(unittest.TestCase): """Tests methods in SlimsClient class""" + example_client: SlimsClient + example_fetch_unit_response: list[Record] + example_fetch_mouse_response: list[Record] + example_fetch_user_response: list[Record] + example_fetch_attachment_response: list[Record] + @classmethod def setUpClass(cls): """Sets up class by downloading responses""" @@ -42,6 +49,8 @@ def get_response(attribute_name: str): cls.example_fetch_unit_response = get_response("example_fetch_unit_response") cls.example_fetch_mouse_response = get_response("example_fetch_mouse_response") cls.example_fetch_user_response = get_response("example_fetch_user_response") + cls.example_fetch_attachment_response = get_response( + "example_fetch_attachments_response.json_entity") def test_rest_link(self): """Tests rest_link method with both queries and no queries.""" @@ -175,6 +184,22 @@ def test_update_failure( mock_update.assert_not_called() mock_log.assert_not_called() + @patch("slims.slims.Slims.fetch_by_pk") + @patch("logging.Logger.info") + @patch("slims.internal.Record.update") + def test_update_model_no_pk( + self, + mock_update: MagicMock, + mock_log: MagicMock, + mock_fetch_by_pk: MagicMock, + ): + """Tests update method when a failure occurs""" + mock_fetch_by_pk.return_value = None + with self.assertRaises(ValueError): + self.example_client.update_model(SlimsUnit.model_construct(pk=None)) + mock_update.assert_not_called() + mock_log.assert_not_called() + @patch("logging.Logger.info") @patch("slims.slims.Slims.add") def test_add_model(self, mock_slims_add: MagicMock, mock_log: MagicMock): @@ -213,6 +238,62 @@ def test_update_model( self.assertEqual(updated_model, returned_model) mock_log.assert_called_once_with("SLIMS Update: Unit/31") + def test_fetch_attachments(self): + # slims_api is dynamically added to slims client + assert len(self.example_fetch_attachment_response) == 1 + with patch.object( + self.example_client.db.slims_api, + "get_entities", + return_value=self.example_fetch_attachment_response + ): + unit = SlimsUnit.model_validate(Record( + json_entity=self.example_fetch_unit_response[0].json_entity, + slims_api=self.example_client.db.slims_api + )) + attachments = self.example_client.fetch_attachments( + unit, + ) + assert len(attachments) == 1 + + def test_fetch_attachment_content(self): + # slims_api is dynamically added to slims client + with patch.object( + self.example_client.db.slims_api, + "get", + return_value=Response(), + ): + self.example_client.fetch_attachment_content( + SlimsAttachment( + attm_name="test", + attm_pk=1, + ) + ) + + @patch("logging.Logger.error") + def test__validate_model_invalid_model(self, mock_log: MagicMock): + valid_data = deepcopy( + self.example_fetch_unit_response[0].json_entity) + invalid_data = deepcopy( + self.example_fetch_unit_response[0].json_entity) + invalid_data["columns"][0]["value"] = 1 + validated = self.example_client._validate_models(SlimsUnit, [ + Record( + json_entity=valid_data, + slims_api=self.example_client.db.slims_api, + ), + Record( + json_entity=invalid_data, + slims_api=self.example_client.db.slims_api, + ), + ]) + assert len(validated) == 1 + assert mock_log.call_count == 1 + + def test_resolve_model_alias_invalid(self): + with self.assertRaises(ValueError): + self.example_client.resolve_model_alias( + SlimsUnit, "not_an_alias") + if __name__ == "__main__": unittest.main() diff --git a/tests/test_instrument.py b/tests/test_instrument.py index f6c8d0d..8c5ae9c 100644 --- a/tests/test_instrument.py +++ b/tests/test_instrument.py @@ -3,16 +3,13 @@ import json import os import unittest -from copy import deepcopy from pathlib import Path from unittest.mock import MagicMock, patch from slims.internal import Record from aind_slims_api.core import SlimsClient -from aind_slims_api.instrument import ( - fetch_instrument_content, -) +from aind_slims_api.instrument import SlimsInstrument RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__))) / "resources" @@ -38,46 +35,18 @@ def setUpClass(cls): ) ] - @patch("logging.Logger.warning") @patch("slims.slims.Slims.fetch") def test_fetch_content_success( self, mock_fetch: MagicMock, - mock_log_warn: MagicMock, ): """Test fetch_instrument_content when successful and multiple are returned from fetch """ mock_fetch.return_value = self.example_response + self.example_response - response = fetch_instrument_content(self.example_client, "323_EPHYS1_OPTO") + response = self.example_client.fetch_model( + SlimsInstrument, name="323_EPHYS1_OPTO") self.assertEqual(response.json_entity, self.example_response[0].json_entity) - self.assertTrue(mock_log_warn.called) - - @patch("slims.slims.Slims.fetch") - def test_fetch_fail( - self, - mock_fetch: MagicMock, - ): - """Test fetch_instrument_content when invalid instrument name is given.""" - mock_fetch.return_value = [] - response = fetch_instrument_content( - self.example_client, "Hopefully not a valid instrument name right?" - ) - self.assertTrue(response is None) - - @patch("slims.slims.Slims.fetch") - def test_fetch_unvalidated_success( - self, - mock_fetch: MagicMock, - ): - """Test fetch_instrument_content when unvalidated instrument data - returned. - """ - bad_return = deepcopy(self.example_response[0]) - bad_return.nstr_pk.value = "burrito" - mock_fetch.return_value = [bad_return, bad_return] - response = fetch_instrument_content(self.example_client, "323_EPHYS1_OPTO") - self.assertTrue(isinstance(response, dict)) if __name__ == "__main__": diff --git a/tests/test_mouse.py b/tests/test_mouse.py index 9a8f72d..d9093be 100644 --- a/tests/test_mouse.py +++ b/tests/test_mouse.py @@ -5,12 +5,11 @@ import unittest from pathlib import Path from unittest.mock import MagicMock, patch -from copy import deepcopy from slims.internal import Record from aind_slims_api.core import SlimsClient -from aind_slims_api.mouse import fetch_mouse_content +from aind_slims_api.mouse import SlimsMouseContent RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__))) / "resources" @@ -18,6 +17,9 @@ class TestMouse(unittest.TestCase): """Tests top level methods in mouse module""" + example_client: SlimsClient + example_fetch_mouse_response: list[Record] + @classmethod def setUpClass(cls): """Load json files of expected responses from slims""" @@ -36,56 +38,12 @@ def setUpClass(cls): def test_fetch_mouse_content_success(self, mock_fetch: MagicMock): """Test fetch_mouse_content when successful""" mock_fetch.return_value = self.example_fetch_mouse_response - mouse_details = fetch_mouse_content(self.example_client, mouse_name="123456") + mouse_details = self.example_client.fetch_model( + SlimsMouseContent, barcode="123456") self.assertEqual( self.example_fetch_mouse_response[0].json_entity, mouse_details.json_entity ) - @patch("logging.Logger.warning") - @patch("slims.slims.Slims.fetch") - def test_fetch_mouse_content_no_mouse( - self, mock_fetch: MagicMock, mock_log_warn: MagicMock - ): - """Test fetch_mouse_content when no mouse is returned""" - mock_fetch.return_value = [] - mouse_details = fetch_mouse_content(self.example_client, mouse_name="12") - self.assertIsNone(mouse_details) - mock_log_warn.assert_called_with("Warning, Mouse not in SLIMS") - - @patch("logging.Logger.warning") - @patch("slims.slims.Slims.fetch") - def test_fetch_mouse_content_many_mouse( - self, mock_fetch: MagicMock, mock_log_warn: MagicMock - ): - """Test fetch_mouse_content when too many mice are returned""" - mock_fetch.return_value = [ - self.example_fetch_mouse_response[0], - self.example_fetch_mouse_response[0], - ] - mouse_details = fetch_mouse_content(self.example_client, mouse_name="123456") - self.assertEqual( - self.example_fetch_mouse_response[0].json_entity, mouse_details.json_entity - ) - mock_log_warn.assert_called_with( - "Warning, Multiple mice in SLIMS with barcode 123456, using pk=3038" - ) - - @patch("logging.Logger.error") - @patch("slims.slims.Slims.fetch") - def test_fetch_mouse_content_validation_fail( - self, mock_fetch: MagicMock, mock_log_error: MagicMock - ): - """Test fetch_mouse when successful""" - wrong_return = deepcopy(self.example_fetch_mouse_response) - wrong_return[0].cntn_cf_waterRestricted.value = 14 - mock_fetch.return_value = wrong_return - mouse_info = fetch_mouse_content(self.example_client, mouse_name="123456") - self.assertEqual( - self.example_fetch_mouse_response[0].json_entity, - mouse_info, - ) - mock_log_error.assert_called() - if __name__ == "__main__": unittest.main() diff --git a/tests/test_user.py b/tests/test_user.py index 1ea9a81..eb6bf83 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -5,12 +5,11 @@ import unittest from pathlib import Path from unittest.mock import MagicMock, patch -from copy import deepcopy from slims.internal import Record from aind_slims_api.core import SlimsClient -from aind_slims_api.user import fetch_user +from aind_slims_api.user import SlimsUser RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__))) / "resources" @@ -18,6 +17,9 @@ class TestUser(unittest.TestCase): """Tests top level methods in user module""" + example_client: SlimsClient + example_fetch_user_response: list[Record] + @classmethod def setUpClass(cls): """Load json files of expected responses from slims""" @@ -36,62 +38,12 @@ def setUpClass(cls): def test_fetch_user_content_success(self, mock_fetch: MagicMock): """Test fetch_user when successful""" mock_fetch.return_value = self.example_fetch_user_response - user_info = fetch_user(self.example_client, username="PersonA") - self.assertEqual( - self.example_fetch_user_response[0].json_entity, - user_info.json_entity, - ) - - @patch("logging.Logger.error") - @patch("slims.slims.Slims.fetch") - def test_fetch_user_content_validation_fail( - self, mock_fetch: MagicMock, mock_log_error: MagicMock - ): - """Test fetch_user when successful""" - wrong_return = deepcopy(self.example_fetch_user_response) - wrong_return[0].user_userName.value = 14 - mock_fetch.return_value = wrong_return - user_info = fetch_user(self.example_client, username="PersonA") - self.assertEqual( - self.example_fetch_user_response[0].json_entity, - user_info, - ) - mock_log_error.assert_called() - - @patch("logging.Logger.warning") - @patch("slims.slims.Slims.fetch") - def test_fetch_user_content_no_user( - self, mock_fetch: MagicMock, mock_log_warn: MagicMock - ): - """Test fetch_user when no user is returned""" - mock_fetch.return_value = [] - user_info = fetch_user(self.example_client, username="PersonX") - self.assertIsNone(user_info) - mock_log_warn.assert_called_with("Warning, User not in SLIMS") - - @patch("logging.Logger.warning") - @patch("slims.slims.Slims.fetch") - def test_fetch_user_content_many_users( - self, mock_fetch: MagicMock, mock_log_warn: MagicMock - ): - """Test fetch_user_content when too many users are returned""" - mocked_response = [ - self.example_fetch_user_response[0], - self.example_fetch_user_response[0], - ] - mock_fetch.return_value = mocked_response - username = "PersonA" - user_info = fetch_user(self.example_client, username=username) + user_info = self.example_client.fetch_model( + SlimsUser, username="PersonA") self.assertEqual( self.example_fetch_user_response[0].json_entity, user_info.json_entity, ) - expected_warning = ( - f"Warning, Multiple users in SLIMS with " - f"username {username}, " - f"using pk={mocked_response[0].pk()}" - ) - mock_log_warn.assert_called_with(expected_warning) if __name__ == "__main__":