diff --git a/src/aind_slims_api/behavior_session.py b/src/aind_slims_api/behavior_session.py new file mode 100644 index 0000000..cfaff44 --- /dev/null +++ b/src/aind_slims_api/behavior_session.py @@ -0,0 +1,123 @@ +"""Contains a model for the behavior session content events, a method for + fetching it and writing it. +""" + +import logging +from typing import Any +from datetime import datetime + +from pydantic import Field + +from aind_slims_api.core import SlimsBaseModel, SlimsClient, SLIMSTABLES + +logger = logging.getLogger() + + +class SlimsBehaviorSessionContentEvent(SlimsBaseModel): + """Model for an instance of the Behavior Session ContentEvent""" + + pk: int | None = Field(default=None, alias="cnvn_pk") + mouse_pk: int | None = Field( + default=None, alias="cnvn_fk_content" + ) # 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") + instrument: int | None = Field(default=None, alias="cnvn_cf_fk_instrument") + trainers: list[int] = Field(default=[], alias="cnvn_cf_fk_trainer") + task: str | None = Field(default=None, alias="cnvn_cf_task") + is_curriculum_suggestion: bool | None = Field( + default=None, alias="cnvn_cf_stageIsOnCurriculum" + ) + task_schema_version: str | None = Field( + default=None, alias="cnvn_cf_taskSchemaVersion" + ) + software_version: str | None = Field(default=None, alias="cnvn_cf_softwareVersion") + date: datetime | None = Field(..., alias="cnvn_cf_scheduledDate") + + cnvn_fk_contentEventType: int = 10 # pk of Behavior Session ContentEvent + + _slims_table: SLIMSTABLES = "ContentEvent" + + +SlimsSingletonFetchReturn = SlimsBaseModel | dict[str, Any] | None + + +def _resolve_pk( + model: SlimsSingletonFetchReturn, + primary_key_name: str = "pk", +) -> int: + """Utility function shared across read/write + + Notes + ----- + - TODO: Change return type of fetch_mouse_content to match pattern in + fetch_behavior_session_content_events, or the other way around? + - TODO: Move to core to have better centralized control of when references + are resolved + """ + if isinstance(model, dict): + logger.warning("Extracting primary key from unvalidated dict.") + return model[primary_key_name] + elif isinstance(model, SlimsBaseModel): + return getattr(model, primary_key_name) + elif model is None: + raise ValueError(f"Cannot resolve primary key from {model}") + else: + raise ValueError("Unexpected type for model: %s" % type(model)) + + +def fetch_behavior_session_content_events( + client: SlimsClient, + mouse: SlimsSingletonFetchReturn, +) -> tuple[list[SlimsBehaviorSessionContentEvent], list[dict[str, Any]]]: + """Fetches behavior sessions for a mouse with labtracks id {mouse_name} + + Returns + ------- + tuple: + list: + Validated SlimsBehaviorSessionContentEvent objects + list: + Dictionaries representations of objects that failed validation + """ + return client.fetch_models( + SlimsBehaviorSessionContentEvent, + cnvn_fk_content=_resolve_pk(mouse), + cnvt_name="Behavior Session", + sort=["cnvn_cf_scheduledDate"], + ) + + +def write_behavior_session_content_events( + client: SlimsClient, + mouse: SlimsSingletonFetchReturn, + instrument: SlimsSingletonFetchReturn, + trainers: list[SlimsSingletonFetchReturn], + *behavior_sessions: SlimsBehaviorSessionContentEvent, +) -> list[SlimsBehaviorSessionContentEvent]: + """Writes behavior sessions for a mouse with labtracks id {mouse_name} + + Notes + ----- + - All supplied `behavior_sessions` will have their `mouse_name` field set + to the value supplied as `mouse_name` to this function + """ + mouse_pk = _resolve_pk(mouse) + logger.debug(f"Mouse pk: {mouse_pk}") + instrument_pk = _resolve_pk(instrument) + logger.debug(f"Instrument pk: {instrument_pk}") + trainer_pks = [_resolve_pk(trainer) for trainer in trainers] + logger.debug(f"Trainer pks: {trainer_pks}") + added = [] + for behavior_session in behavior_sessions: + updated = behavior_session.model_copy( + update={ + "mouse_pk": mouse_pk, + "instrument": instrument_pk, + "trainers": trainer_pks, + }, + ) + logger.debug(f"Resolved behavior session: {updated}") + added.append(client.add_model(updated)) + + return added diff --git a/src/aind_slims_api/core.py b/src/aind_slims_api/core.py index f7646cf..b94844e 100644 --- a/src/aind_slims_api/core.py +++ b/src/aind_slims_api/core.py @@ -13,12 +13,13 @@ from pydantic import ( BaseModel, ValidationInfo, + ValidationError, field_serializer, field_validator, ) from pydantic.fields import FieldInfo import logging -from typing import Literal, Optional +from typing import Any, Literal, Optional, Type, TypeVar from slims.slims import Slims, _SlimsApiException from slims.internal import ( @@ -41,6 +42,7 @@ "Test", "User", "Groups", + "Instrument", ] @@ -132,6 +134,9 @@ def _serialize(self, field, info): # TODO: Support attachments +SlimsBaseModelTypeVar = TypeVar("SlimsBaseModelTypeVar", bound=SlimsBaseModel) + + class SlimsClient: """Wrapper around slims-python-api client with convenience methods""" @@ -199,6 +204,44 @@ def fetch( return records + def fetch_models( + self, + model: Type[SlimsBaseModelTypeVar], + *args, + sort: Optional[str | list[str]] = None, + start: Optional[int] = None, + end: Optional[int] = None, + **kwargs, + ) -> tuple[list[SlimsBaseModelTypeVar], list[dict[str, Any]]]: + """Fetch records from SLIMS and return them as SlimsBaseModel objects + + Returns + ------- + tuple: + list: + Validated SlimsBaseModel objects + list: + Dictionaries representations of objects that failed validation + """ + response = self.fetch( + model._slims_table.default, # TODO: consider changing fetch method + *args, + sort=sort, + start=start, + end=end, + **kwargs, + ) + validated = [] + unvalidated = [] + for record in response: + try: + validated.append(model.model_validate(record)) + except ValidationError as e: + logger.error(f"SLIMS data validation failed, {repr(e)}") + unvalidated.append(record.json_entity) + + return validated, unvalidated + @lru_cache(maxsize=None) def fetch_pk(self, table: SLIMSTABLES, *args, **kwargs) -> int | None: """SlimsClient.fetch but returns the pk of the first returned record""" @@ -233,7 +276,9 @@ def rest_link(self, table: SLIMSTABLES, **kwargs): queries = [f"?{k}={v}" for k, v in kwargs.items()] return base_url + "".join(queries) - def add_model(self, model: SlimsBaseModel, *args, **kwargs) -> SlimsBaseModel: + def add_model( + self, model: SlimsBaseModelTypeVar, *args, **kwargs + ) -> SlimsBaseModelTypeVar: """Given a SlimsBaseModel object, add it to SLIMS Args model (SlimsBaseModel): object to add diff --git a/src/aind_slims_api/instrument.py b/src/aind_slims_api/instrument.py new file mode 100644 index 0000000..4314fcf --- /dev/null +++ b/src/aind_slims_api/instrument.py @@ -0,0 +1,66 @@ +"""Contains a model for the instrument content, and a method for fetching it""" + +import logging +from typing import Any + +from pydantic import Field + +from aind_slims_api.core import SlimsBaseModel, SlimsClient, SLIMSTABLES + +logger = logging.getLogger() + + +class SlimsInstrument(SlimsBaseModel): + """Model for an instance of the Behavior Session ContentEvent""" + + name: str = Field(..., alias="nstr_name") + pk: int = Field(..., alias="nstr_pk") + _slims_table: SLIMSTABLES = "Instrument" + + # todo add more useful fields + + +def fetch_instrument_content( + client: SlimsClient, + instrument_name: str, +) -> SlimsInstrument | dict[str, Any] | None: + """Fetches behavior sessions for a mouse with labtracks id {mouse_name} + + Returns + ------- + tuple: + list: + Validated SlimsInstrument objects + list: + Dictionaries representations of objects that failed validation + + Notes + ----- + - Todo: add partial name match or some other type of filtering + - TODO: reconsider this pattern, consider just returning all records or + having number returned be a parameter or setting + """ + validated, unvalidated = client.fetch_models( + SlimsInstrument, + nstr_name=instrument_name, + ) + if len(validated) > 0: + validated_details = validated[0] + if len(validated) > 1: + logger.warning( + f"Warning, Multiple instruments in SLIMS with name {instrument_name}, " + f"using pk={validated_details.pk}" + ) + return validated_details + else: + if len(unvalidated) > 0: + unvalidated_details = unvalidated[0] + if len(unvalidated) > 1: + logger.warning( + "Warning, Multiple instruments in SLIMS with name " + f"{instrument_name}, " + f"using pk={unvalidated_details['pk']}" + ) + return unvalidated[0] + + return None diff --git a/src/aind_slims_api/mouse.py b/src/aind_slims_api/mouse.py index f11717e..ad3a439 100644 --- a/src/aind_slims_api/mouse.py +++ b/src/aind_slims_api/mouse.py @@ -62,7 +62,7 @@ def fetch_mouse_content( ) else: logger.warning("Warning, Mouse not in SLIMS") - return + return None try: mouse = SlimsMouseContent.model_validate(mouse_details) diff --git a/tests/resources/example_fetch_behavior_session_content_events_response.json_entity.json b/tests/resources/example_fetch_behavior_session_content_events_response.json_entity.json new file mode 100644 index 0000000..8500a96 --- /dev/null +++ b/tests/resources/example_fetch_behavior_session_content_events_response.json_entity.json @@ -0,0 +1 @@ +[{"pk": 63, "tableName": "ContentEvent", "columns": [{"datatype": "FOREIGN_KEY", "name": "cnvn_fk_study", "title": "Study", "position": 0, "value": null, "hidden": false, "editable": true, "foreignTable": "Study", "displayValue": null, "displayField": "stud_name", "foreignDisplayColumn": "stud_name"}, {"datatype": "DATE", "name": "cnvn_cf_scheduledDate", "title": "Scheduled Date", "position": 1, "value": 1609488000000, "hidden": false, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "DATE", "name": "cnvn_date", "title": "Date", "position": 2, "value": null, "hidden": true, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "DATE", "name": "cnvn_dateFrom", "title": "From date", "position": 3, "value": null, "hidden": true, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_contentEventType", "title": "Content event type", "position": 4, "value": 10, "hidden": false, "editable": false, "foreignTable": "ContentEventType", "displayValue": "Behavior Session", "displayField": "cnvt_name", "foreignDisplayColumn": "cnvt_name"}, {"datatype": "DATE", "name": "cnvn_dateUntil", "title": "Until date", "position": 5, "value": null, "hidden": true, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "STRING", "name": "cnvn_cf_task", "title": "Task", "position": 6, "value": "Test", "hidden": false, "editable": true}, {"datatype": "MULTIPLE_FOREIGN_KEY", "name": "cnvn_cf_fk_trainer", "title": "Trainer", "position": 7, "value": [19], "hidden": false, "editable": true, "foreignTable": "User", "displayValues": ["ClarkR"], "foreignDisplayColumn": "user_userName", "joinedDisplayValue": "ClarkR"}, {"datatype": "FOREIGN_KEY", "name": "cnvn_cf_fk_instrument", "title": "Instrument", "position": 8, "value": 1743, "hidden": false, "editable": true, "foreignTable": "ReferenceDataRecord", "displayValue": "323_EPHYS1_OPTO_20240212", "displayField": "cnvn_cf_fk_instrument_display", "foreignDisplayColumn": "rdrc_name"}, {"datatype": "BOOLEAN", "name": "cnvn_cf_stageIsOnCurriculum", "title": "Stage Is On Curriculum", "position": 9, "value": true, "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_taskStage", "title": "Task Stage", "position": 10, "value": "Test", "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_taskSchemaVersion", "title": "Task Schema Version", "position": 11, "value": "Test", "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_softwareVersion", "title": "Software Version", "position": 12, "value": "Test", "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_notes", "title": "Notes", "position": 13, "value": "Test", "hidden": false, "editable": true}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_content", "title": "Content", "position": 14, "value": 1, "hidden": false, "editable": true, "foreignTable": "Content", "displayValue": "00000000", "displayField": "cntn_id", "foreignDisplayColumn": "cntn_id"}, {"datatype": "STRING", "name": "cnvn_barCode", "title": "Barcode", "position": 15, "value": "CE00000068", "hidden": false, "editable": true}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_group", "title": "Group", "position": 16, "value": null, "hidden": false, "editable": true, "foreignTable": "Groups", "displayValue": null, "displayField": "grps_groupName", "foreignDisplayColumn": "grps_groupName"}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_experimentRunStep", "title": "Experiment run step", "position": 17, "value": null, "hidden": true, "editable": true, "foreignTable": "ExperimentRunStep", "displayValue": null, "displayField": "xprs_name", "foreignDisplayColumn": "xprs_name"}, {"datatype": "STRING", "name": "cnvn_createdBy", "title": "Created by", "position": 18, "value": "SIPE", "hidden": false, "editable": true}, {"datatype": "DATE", "name": "cnvn_createdOn", "title": "Created on", "position": 19, "value": 1719519578574, "hidden": false, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "STRING", "name": "cnvn_modifiedBy", "title": "Modified by", "position": 20, "value": "SIPE", "hidden": false, "editable": true}, {"datatype": "DATE", "name": "cnvn_modifiedOn", "title": "Modified on", "position": 21, "value": 1719519578574, "hidden": false, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "STRING", "name": "stud_name", "title": "Name", "position": 22, "value": null, "hidden": true, "editable": true}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_user", "title": "User", "position": 23, "value": null, "hidden": true, "editable": true, "foreignTable": "User", "displayValue": null, "displayField": "user_userName", "foreignDisplayColumn": "user_userName"}, {"datatype": "STRING", "name": "cntn_id", "title": "Id", "position": 24, "value": "00000000", "hidden": true, "editable": true}, {"datatype": "INTEGER", "name": "cnvn_pk", "title": "cnvn_pk", "position": 25, "value": 63, "hidden": true, "editable": true}, {"datatype": "STRING", "name": "xprs_name", "title": "Name", "position": 26, "value": null, "hidden": true, "editable": true}, {"datatype": "STRING", "name": "cnvt_name", "title": "Name", "position": 27, "value": "Behavior Session", "hidden": true, "editable": true}], "canUpdate": true, "canDelete": true, "links": [{"rel": "self", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ContentEvent/63"}, {"rel": "cnvn_fk_contentEventType", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ContentEventType/10"}, {"rel": "cnvn_cf_fk_instrument", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ReferenceDataRecord/1743"}, {"rel": "cnvn_fk_content", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Content/1"}, {"rel": "-crfr_fk_contentEvent", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/CaseReportForm?crfr_fk_contentEvent=63"}, {"rel": "-qrcv_fk_contentEvent", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/QueueElementRequestContentEvent?qrcv_fk_contentEvent=63"}, {"rel": "-wfsf_fk_contentEvent", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/WorkflowScheduleFlag?wfsf_fk_contentEvent=63"}, {"rel": "attachments", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/attachment/ContentEvent/63"}]}, {"pk": 64, "tableName": "ContentEvent", "columns": [{"datatype": "FOREIGN_KEY", "name": "cnvn_fk_study", "title": "Study", "position": 0, "value": null, "hidden": false, "editable": true, "foreignTable": "Study", "displayValue": null, "displayField": "stud_name", "foreignDisplayColumn": "stud_name"}, {"datatype": "DATE", "name": "cnvn_cf_scheduledDate", "title": "Scheduled Date", "position": 1, "value": 1609488000000, "hidden": false, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "DATE", "name": "cnvn_date", "title": "Date", "position": 2, "value": null, "hidden": true, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "DATE", "name": "cnvn_dateFrom", "title": "From date", "position": 3, "value": null, "hidden": true, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_contentEventType", "title": "Content event type", "position": 4, "value": 10, "hidden": false, "editable": false, "foreignTable": "ContentEventType", "displayValue": "Behavior Session", "displayField": "cnvt_name", "foreignDisplayColumn": "cnvt_name"}, {"datatype": "DATE", "name": "cnvn_dateUntil", "title": "Until date", "position": 5, "value": null, "hidden": true, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "STRING", "name": "cnvn_cf_task", "title": "Task", "position": 6, "value": "Test", "hidden": false, "editable": true}, {"datatype": "MULTIPLE_FOREIGN_KEY", "name": "cnvn_cf_fk_trainer", "title": "Trainer", "position": 7, "value": [19], "hidden": false, "editable": true, "foreignTable": "User", "displayValues": ["ClarkR"], "foreignDisplayColumn": "user_userName", "joinedDisplayValue": "ClarkR"}, {"datatype": "FOREIGN_KEY", "name": "cnvn_cf_fk_instrument", "title": "Instrument", "position": 8, "value": 1743, "hidden": false, "editable": true, "foreignTable": "ReferenceDataRecord", "displayValue": "323_EPHYS1_OPTO_20240212", "displayField": "cnvn_cf_fk_instrument_display", "foreignDisplayColumn": "rdrc_name"}, {"datatype": "BOOLEAN", "name": "cnvn_cf_stageIsOnCurriculum", "title": "Stage Is On Curriculum", "position": 9, "value": true, "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_taskStage", "title": "Task Stage", "position": 10, "value": "Test", "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_taskSchemaVersion", "title": "Task Schema Version", "position": 11, "value": "Test", "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_softwareVersion", "title": "Software Version", "position": 12, "value": "Test", "hidden": false, "editable": true}, {"datatype": "STRING", "name": "cnvn_cf_notes", "title": "Notes", "position": 13, "value": "Test", "hidden": false, "editable": true}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_content", "title": "Content", "position": 14, "value": 1, "hidden": false, "editable": true, "foreignTable": "Content", "displayValue": "00000000", "displayField": "cntn_id", "foreignDisplayColumn": "cntn_id"}, {"datatype": "STRING", "name": "cnvn_barCode", "title": "Barcode", "position": 15, "value": "CE00000069", "hidden": false, "editable": true}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_group", "title": "Group", "position": 16, "value": null, "hidden": false, "editable": true, "foreignTable": "Groups", "displayValue": null, "displayField": "grps_groupName", "foreignDisplayColumn": "grps_groupName"}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_experimentRunStep", "title": "Experiment run step", "position": 17, "value": null, "hidden": true, "editable": true, "foreignTable": "ExperimentRunStep", "displayValue": null, "displayField": "xprs_name", "foreignDisplayColumn": "xprs_name"}, {"datatype": "STRING", "name": "cnvn_createdBy", "title": "Created by", "position": 18, "value": "SIPE", "hidden": false, "editable": true}, {"datatype": "DATE", "name": "cnvn_createdOn", "title": "Created on", "position": 19, "value": 1719519770162, "hidden": false, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "STRING", "name": "cnvn_modifiedBy", "title": "Modified by", "position": 20, "value": "SIPE", "hidden": false, "editable": true}, {"datatype": "DATE", "name": "cnvn_modifiedOn", "title": "Modified on", "position": 21, "value": 1719519770162, "hidden": false, "editable": true, "dateFormat": "MM/dd/yyyy HH:mm:ss", "subType": "datetime", "timeZone": "America/Los_Angeles"}, {"datatype": "STRING", "name": "stud_name", "title": "Name", "position": 22, "value": null, "hidden": true, "editable": true}, {"datatype": "FOREIGN_KEY", "name": "cnvn_fk_user", "title": "User", "position": 23, "value": null, "hidden": true, "editable": true, "foreignTable": "User", "displayValue": null, "displayField": "user_userName", "foreignDisplayColumn": "user_userName"}, {"datatype": "STRING", "name": "cntn_id", "title": "Id", "position": 24, "value": "00000000", "hidden": true, "editable": true}, {"datatype": "INTEGER", "name": "cnvn_pk", "title": "cnvn_pk", "position": 25, "value": 64, "hidden": true, "editable": true}, {"datatype": "STRING", "name": "xprs_name", "title": "Name", "position": 26, "value": null, "hidden": true, "editable": true}, {"datatype": "STRING", "name": "cnvt_name", "title": "Name", "position": 27, "value": "Behavior Session", "hidden": true, "editable": true}], "canUpdate": true, "canDelete": true, "links": [{"rel": "self", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ContentEvent/64"}, {"rel": "cnvn_fk_contentEventType", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ContentEventType/10"}, {"rel": "cnvn_cf_fk_instrument", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ReferenceDataRecord/1743"}, {"rel": "cnvn_fk_content", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Content/1"}, {"rel": "-crfr_fk_contentEvent", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/CaseReportForm?crfr_fk_contentEvent=64"}, {"rel": "-qrcv_fk_contentEvent", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/QueueElementRequestContentEvent?qrcv_fk_contentEvent=64"}, {"rel": "-wfsf_fk_contentEvent", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/WorkflowScheduleFlag?wfsf_fk_contentEvent=64"}, {"rel": "attachments", "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/attachment/ContentEvent/64"}]}] \ No newline at end of file diff --git a/tests/resources/example_fetch_instrument_response.json_entity.json b/tests/resources/example_fetch_instrument_response.json_entity.json new file mode 100644 index 0000000..c76de2f --- /dev/null +++ b/tests/resources/example_fetch_instrument_response.json_entity.json @@ -0,0 +1,294 @@ +[ + { + "pk": 4, + "tableName": "Instrument", + "columns": [ + { + "datatype": "STRING", + "name": "nstr_name", + "title": "Name", + "position": 0, + "value": "323_EPHYS1_OPTO", + "hidden": false, + "editable": true + }, + { + "datatype": "ENUM", + "name": "icon", + "title": "Icon", + "position": 1, + "value": "data_icons/rat.png", + "hidden": false, + "editable": true, + "displayValue": "data_icons/rat.png" + }, + { + "datatype": "STRING", + "name": "nstr_uniqueIdentifier", + "title": "Unique identifier", + "position": 2, + "value": "nstr_323_dot_rig1", + "hidden": false, + "editable": true + }, + { + "datatype": "STRING", + "name": "nstr_description", + "title": "Description", + "position": 3, + "value": "
EPHYS1 Rig configuration for opto experiments
\n\nMain User: Burt Rappapor
\n", + "hidden": false, + "editable": true + }, + { + "datatype": "FOREIGN_KEY", + "name": "nstr_fk_instrumentType", + "title": "Instrument type", + "position": 4, + "value": 3, + "hidden": false, + "editable": false, + "foreignTable": "InstrumentType", + "displayValue": "Behavior and Physiology Rig", + "displayField": "nstp_name", + "foreignDisplayColumn": "nstp_name" + }, + { + "datatype": "FOREIGN_KEY", + "name": "nstr_fk_user", + "title": "User", + "position": 5, + "value": 7, + "hidden": false, + "editable": true, + "foreignTable": "User", + "displayValue": "LKim", + "displayField": "user_userName", + "foreignDisplayColumn": "user_userName" + }, + { + "datatype": "FOREIGN_KEY", + "name": "nstr_fk_group", + "title": "Group", + "position": 6, + "value": null, + "hidden": false, + "editable": true, + "foreignTable": "Groups", + "displayValue": null, + "displayField": "grps_groupName", + "foreignDisplayColumn": "grps_groupName" + }, + { + "datatype": "FOREIGN_KEY", + "name": "nstr_fk_status", + "title": "Status", + "position": 7, + "value": 34, + "hidden": false, + "editable": true, + "foreignTable": "Status", + "displayValue": "Available", + "displayField": "stts_name", + "foreignDisplayColumn": "stts_name" + }, + { + "datatype": "BOOLEAN", + "name": "nstr_active", + "title": "Active", + "position": 8, + "value": true, + "hidden": false, + "editable": true + }, + { + "datatype": "STRING", + "name": "nstr_cf_facilitiesId", + "title": "Facilities ID", + "position": 9, + "value": null, + "hidden": false, + "editable": true + }, + { + "datatype": "FOREIGN_KEY", + "name": "nstr_cf_fk_instrumentLocation", + "title": "Instrument Location", + "position": 10, + "value": null, + "hidden": false, + "editable": true, + "foreignTable": "Location", + "displayValue": null, + "displayField": "nstr_cf_fk_instrumentLocation_display", + "foreignDisplayColumn": "lctn_name" + }, + { + "datatype": "BOOLEAN", + "name": "nstr_calibrated", + "title": "Calibrated", + "position": 11, + "value": false, + "hidden": false, + "editable": true + }, + { + "datatype": "DATE", + "name": "nstr_calibrationExpiryDate", + "title": "Calibration expiry date", + "position": 12, + "value": null, + "hidden": false, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "STRING", + "name": "nstr_createdBy", + "title": "Created by", + "position": 13, + "value": "LKim", + "hidden": false, + "editable": true + }, + { + "datatype": "DATE", + "name": "nstr_createdOn", + "title": "Created on", + "position": 14, + "value": 1698096044392, + "hidden": false, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "STRING", + "name": "nstr_modifiedBy", + "title": "Modified by", + "position": 15, + "value": "LKim", + "hidden": false, + "editable": true + }, + { + "datatype": "DATE", + "name": "nstr_modifiedOn", + "title": "Modified on", + "position": 16, + "value": 1716321087534, + "hidden": false, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "INTEGER", + "name": "nstr_pk", + "title": "nstr_pk", + "position": 17, + "value": 4, + "hidden": true, + "editable": true + }, + { + "datatype": "STRING", + "name": "nstp_name", + "title": "Name", + "position": 18, + "value": "Behavior and Physiology Rig", + "hidden": true, + "editable": true + }, + { + "datatype": "FOREIGN_KEY", + "name": "nstr_fk_calibrationRun", + "title": "nstr_fk_calibrationRun", + "position": 19, + "value": null, + "hidden": true, + "editable": true, + "foreignTable": "ExperimentRun", + "displayValue": null, + "displayField": "xprn_name", + "foreignDisplayColumn": "xprn_name" + } + ], + "canUpdate": false, + "canDelete": true, + "links": [ + { + "rel": "self", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Instrument/4" + }, + { + "rel": "nstr_fk_instrumentType", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/InstrumentType/3" + }, + { + "rel": "nstr_fk_user", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/User/7" + }, + { + "rel": "nstr_fk_status", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Status/34" + }, + { + "rel": "-xptm_cf_fk_instrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ExperimentTemplate?xptm_cf_fk_instrument=4" + }, + { + "rel": "-rdrc_cf_fk_rigInstrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ReferenceDataRecord?rdrc_cf_fk_rigInstrument=4" + }, + { + "rel": "-rdrc_cf_fk_deviceName", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ReferenceDataRecord?rdrc_cf_fk_deviceName=4" + }, + { + "rel": "-rdrc_cf_fk_instrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ReferenceDataRecord?rdrc_cf_fk_instrument=4" + }, + { + "rel": "-xprn_fk_instrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ExperimentRun?xprn_fk_instrument=4" + }, + { + "rel": "-xprs_cf_fk_rigInstrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ExperimentRunStep?xprs_cf_fk_rigInstrument=4" + }, + { + "rel": "-rslt_cf_fk_balance", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Result?rslt_cf_fk_balance=4" + }, + { + "rel": "-rslt_cf_fk_workStation", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Result?rslt_cf_fk_workStation=4" + }, + { + "rel": "-rslt_cf_fk_injectionDevice", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Result?rslt_cf_fk_injectionDevice=4" + }, + { + "rel": "-rslt_cf_fk_instrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Result?rslt_cf_fk_instrument=4" + }, + { + "rel": "-xprm_fk_instrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Experiment?xprm_fk_instrument=4" + }, + { + "rel": "-nsrn_fk_instrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/InstrumentRun?nsrn_fk_instrument=4" + }, + { + "rel": "attachments", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/attachment/Instrument/4" + } + ] + } +] \ No newline at end of file diff --git a/tests/resources/example_write_behavior_session_content_events_response.json_entity.json b/tests/resources/example_write_behavior_session_content_events_response.json_entity.json new file mode 100644 index 0000000..e1d8f7c --- /dev/null +++ b/tests/resources/example_write_behavior_session_content_events_response.json_entity.json @@ -0,0 +1,350 @@ +[ + { + "pk": 79, + "tableName": "ContentEvent", + "columns": [ + { + "datatype": "FOREIGN_KEY", + "name": "cnvn_fk_study", + "title": "Study", + "position": 0, + "value": null, + "hidden": false, + "editable": true, + "foreignTable": "Study", + "displayValue": null, + "displayField": "stud_name", + "foreignDisplayColumn": "stud_name" + }, + { + "datatype": "DATE", + "name": "cnvn_cf_scheduledDate", + "title": "Scheduled Date", + "position": 1, + "value": 1609574400000, + "hidden": false, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "DATE", + "name": "cnvn_date", + "title": "Date", + "position": 2, + "value": null, + "hidden": true, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "DATE", + "name": "cnvn_dateFrom", + "title": "From date", + "position": 3, + "value": null, + "hidden": true, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "FOREIGN_KEY", + "name": "cnvn_fk_contentEventType", + "title": "Content event type", + "position": 4, + "value": 10, + "hidden": false, + "editable": false, + "foreignTable": "ContentEventType", + "displayValue": "Behavior Session", + "displayField": "cnvt_name", + "foreignDisplayColumn": "cnvt_name" + }, + { + "datatype": "DATE", + "name": "cnvn_dateUntil", + "title": "Until date", + "position": 5, + "value": null, + "hidden": true, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "STRING", + "name": "cnvn_cf_task", + "title": "Task", + "position": 6, + "value": "Test task", + "hidden": false, + "editable": true + }, + { + "datatype": "MULTIPLE_FOREIGN_KEY", + "name": "cnvn_cf_fk_trainer", + "title": "Trainer", + "position": 7, + "value": [ + 19 + ], + "hidden": false, + "editable": true, + "foreignTable": "User", + "displayValues": [ + "ClarkR" + ], + "foreignDisplayColumn": "user_userName", + "joinedDisplayValue": "ClarkR" + }, + { + "datatype": "FOREIGN_KEY", + "name": "cnvn_cf_fk_instrument", + "title": "Instrument", + "position": 8, + "value": 4, + "hidden": false, + "editable": true, + "foreignTable": "ReferenceDataRecord", + "displayValue": "EasyIndex", + "displayField": "cnvn_cf_fk_instrument_display", + "foreignDisplayColumn": "rdrc_name" + }, + { + "datatype": "BOOLEAN", + "name": "cnvn_cf_stageIsOnCurriculum", + "title": "Stage Is On Curriculum", + "position": 9, + "value": null, + "hidden": false, + "editable": true + }, + { + "datatype": "STRING", + "name": "cnvn_cf_taskStage", + "title": "Task Stage", + "position": 10, + "value": "Test stage", + "hidden": false, + "editable": true + }, + { + "datatype": "STRING", + "name": "cnvn_cf_taskSchemaVersion", + "title": "Task Schema Version", + "position": 11, + "value": null, + "hidden": false, + "editable": true + }, + { + "datatype": "STRING", + "name": "cnvn_cf_softwareVersion", + "title": "Software Version", + "position": 12, + "value": null, + "hidden": false, + "editable": true + }, + { + "datatype": "STRING", + "name": "cnvn_cf_notes", + "title": "Notes", + "position": 13, + "value": "Test notes", + "hidden": false, + "editable": true + }, + { + "datatype": "FOREIGN_KEY", + "name": "cnvn_fk_content", + "title": "Content", + "position": 14, + "value": 1, + "hidden": false, + "editable": true, + "foreignTable": "Content", + "displayValue": "00000000", + "displayField": "cntn_id", + "foreignDisplayColumn": "cntn_id" + }, + { + "datatype": "STRING", + "name": "cnvn_barCode", + "title": "Barcode", + "position": 15, + "value": "CE00000084", + "hidden": false, + "editable": true + }, + { + "datatype": "FOREIGN_KEY", + "name": "cnvn_fk_group", + "title": "Group", + "position": 16, + "value": null, + "hidden": false, + "editable": true, + "foreignTable": "Groups", + "displayValue": null, + "displayField": "grps_groupName", + "foreignDisplayColumn": "grps_groupName" + }, + { + "datatype": "FOREIGN_KEY", + "name": "cnvn_fk_experimentRunStep", + "title": "Experiment run step", + "position": 17, + "value": null, + "hidden": true, + "editable": true, + "foreignTable": "ExperimentRunStep", + "displayValue": null, + "displayField": "xprs_name", + "foreignDisplayColumn": "xprs_name" + }, + { + "datatype": "STRING", + "name": "cnvn_createdBy", + "title": "Created by", + "position": 18, + "value": "SIPE", + "hidden": false, + "editable": true + }, + { + "datatype": "DATE", + "name": "cnvn_createdOn", + "title": "Created on", + "position": 19, + "value": 1719615306529, + "hidden": false, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "STRING", + "name": "cnvn_modifiedBy", + "title": "Modified by", + "position": 20, + "value": "SIPE", + "hidden": false, + "editable": true + }, + { + "datatype": "DATE", + "name": "cnvn_modifiedOn", + "title": "Modified on", + "position": 21, + "value": 1719615306529, + "hidden": false, + "editable": true, + "dateFormat": "MM/dd/yyyy HH:mm:ss", + "subType": "datetime", + "timeZone": "America/Los_Angeles" + }, + { + "datatype": "STRING", + "name": "stud_name", + "title": "Name", + "position": 22, + "value": null, + "hidden": true, + "editable": true + }, + { + "datatype": "FOREIGN_KEY", + "name": "cnvn_fk_user", + "title": "User", + "position": 23, + "value": null, + "hidden": true, + "editable": true, + "foreignTable": "User", + "displayValue": null, + "displayField": "user_userName", + "foreignDisplayColumn": "user_userName" + }, + { + "datatype": "STRING", + "name": "cntn_id", + "title": "Id", + "position": 24, + "value": "00000000", + "hidden": true, + "editable": true + }, + { + "datatype": "INTEGER", + "name": "cnvn_pk", + "title": "cnvn_pk", + "position": 25, + "value": 79, + "hidden": true, + "editable": true + }, + { + "datatype": "STRING", + "name": "xprs_name", + "title": "Name", + "position": 26, + "value": null, + "hidden": true, + "editable": true + }, + { + "datatype": "STRING", + "name": "cnvt_name", + "title": "Name", + "position": 27, + "value": "Behavior Session", + "hidden": true, + "editable": true + } + ], + "canUpdate": true, + "canDelete": true, + "links": [ + { + "rel": "self", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ContentEvent/79" + }, + { + "rel": "cnvn_fk_contentEventType", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ContentEventType/10" + }, + { + "rel": "cnvn_cf_fk_instrument", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/ReferenceDataRecord/4" + }, + { + "rel": "cnvn_fk_content", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/Content/1" + }, + { + "rel": "-crfr_fk_contentEvent", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/CaseReportForm?crfr_fk_contentEvent=79" + }, + { + "rel": "-qrcv_fk_contentEvent", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/QueueElementRequestContentEvent?qrcv_fk_contentEvent=79" + }, + { + "rel": "-wfsf_fk_contentEvent", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/WorkflowScheduleFlag?wfsf_fk_contentEvent=79" + }, + { + "rel": "attachments", + "href": "https://aind-test.us.slims.agilent.com/slimsrest/rest/attachment/ContentEvent/79" + } + ] + } + ] \ No newline at end of file diff --git a/tests/test_behavior_session.py b/tests/test_behavior_session.py new file mode 100644 index 0000000..71644a3 --- /dev/null +++ b/tests/test_behavior_session.py @@ -0,0 +1,174 @@ +"""Tests methods in mouse module""" + +import json +import os +import unittest +from pathlib import Path +from unittest.mock import MagicMock, patch +from datetime import datetime + +from slims.internal import Record + +from aind_slims_api.core import SlimsClient +from aind_slims_api.mouse import ( + SlimsMouseContent, +) +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, +) + +RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__))) / "resources" + + +class TestBehaviorSession(unittest.TestCase): + """Tests top level methods in mouse module""" + + example_client: SlimsClient + example_response: list[Record] + example_mouse_response: list[Record] + example_behavior_sessions: list[SlimsBehaviorSessionContentEvent] + example_mouse: SlimsMouseContent + example_write_sessions_response: list[Record] + example_instrument: SlimsInstrument + example_trainer: SlimsUser + + @classmethod + def setUpClass(cls): + """Load json files of expected responses from slims""" + cls.example_client = SlimsClient( + url="http://fake_url", username="user", password="pass" + ) + cls.example_response = [ + Record(json_entity=r, slims_api=cls.example_client.db.slims_api) + for r in json.loads( + ( + RESOURCES_DIR + / ( + "example_fetch_behavior_session_content_events_response" + ".json_entity.json" + ) + ).read_text() + ) + ] + cls.example_mouse_response = [ + Record(json_entity=r, slims_api=cls.example_client.db.slims_api) + for r in json.loads( + (RESOURCES_DIR / "example_fetch_mouse_response.json").read_text() + ) + ] + assert ( + len(cls.example_response) > 1 + ), "Example response must be greater than 1 for tests to work..." + + cls.example_instrument = SlimsInstrument( + nstr_name="323_EPHYS1_OPTO", + nstr_pk=1, + ) + cls.example_trainer = SlimsUser( + user_userName="ClarkR", + user_pk=1, + ) + cls.example_mouse = SlimsMouseContent( + cntn_barCode="00000000", + cntn_pk=1, + cntn_cf_waterRestricted=False, + cntn_cf_scientificPointOfContact=None, + cntn_cf_baselineWeight=None, + ) + cls.example_behavior_sessions = [ + SlimsBehaviorSessionContentEvent( + cnvn_cf_notes="Test notes", + cnvn_cf_taskStage="Test stage", + cnvn_cf_task="Test task", + cnvn_cf_scheduledDate=datetime(2021, 1, 2), + ), + ] + cls.example_write_sessions_response = [ + Record(json_entity=r, slims_api=cls.example_client.db.slims_api) + for r in json.loads( + ( + RESOURCES_DIR + / ( + "example_write_behavior_session_content_events_response." + "json_entity.json" + ) + ).read_text() + ) + ][0] + + @patch("slims.slims.Slims.fetch") + 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 + ) + 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, + ) + + @patch("slims.slims.Slims.fetch") + def test_fetch_behavior_session_content_events_success_unvalidated( + 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 + ) + 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, + ) + + @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, + mock_add: MagicMock, + ): + """Test write_behavior_session_content_events success""" + mock_add.return_value = self.example_write_sessions_response + added = write_behavior_session_content_events( + self.example_client, + self.example_mouse, + self.example_instrument, + [self.example_trainer], + *self.example_behavior_sessions, + ) + self.assertTrue( + all((item.mouse_pk == self.example_mouse.pk for item in added)) + ) + self.assertTrue(len(added) == len(self.example_behavior_sessions)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_instrument.py b/tests/test_instrument.py new file mode 100644 index 0000000..f6c8d0d --- /dev/null +++ b/tests/test_instrument.py @@ -0,0 +1,84 @@ +"""Tests methods in mouse module""" + +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, +) + +RESOURCES_DIR = Path(os.path.dirname(os.path.realpath(__file__))) / "resources" + + +class TestInstrument(unittest.TestCase): + """Tests top level methods in mouse module""" + + example_client: SlimsClient + example_response: Record + + @classmethod + def setUpClass(cls): + """Load json files of expected responses from slims""" + cls.example_client = SlimsClient( + url="http://fake_url", username="user", password="pass" + ) + cls.example_response = [ + Record(json_entity=r, slims_api=cls.example_client.db.slims_api) + for r in json.loads( + ( + RESOURCES_DIR / "example_fetch_instrument_response.json_entity.json" + ).read_text() + ) + ] + + @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") + 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__": + unittest.main()