|
3 | 3 | import json |
4 | 4 | import unittest |
5 | 5 | from datetime import date |
| 6 | +from unittest.mock import patch |
6 | 7 |
|
7 | 8 | from aind_data_schema_models.coordinates import AnatomicalRelative |
8 | 9 | from aind_data_schema_models.modalities import Modality |
9 | 10 | from aind_data_schema_models.organizations import Organization |
10 | 11 | from aind_data_schema_models.units import FrequencyUnit, PowerUnit, SizeUnit |
| 12 | +from aind_data_schema_models.harp_types import HarpDeviceType |
11 | 13 | from pydantic import ValidationError |
12 | 14 |
|
13 | 15 | from aind_data_schema.components.coordinates import CoordinateSystemLibrary |
|
25 | 27 | EphysAssembly, |
26 | 28 | EphysProbe, |
27 | 29 | FiberPatchCord, |
| 30 | + HarpDevice, |
28 | 31 | Laser, |
29 | 32 | LaserAssembly, |
30 | 33 | Lens, |
@@ -655,8 +658,8 @@ def test_instrument_addition(self): |
655 | 658 | # Check that modalities are combined and sorted (should be the same since we're adding identical instruments) |
656 | 659 | self.assertEqual(len(combined.modalities), len(set(inst1.modalities + inst2.modalities))) |
657 | 660 |
|
658 | | - # Check that components are combined |
659 | | - self.assertEqual(len(combined.components), len(inst1.components) + len(inst2.components)) |
| 661 | + # Check that components are deduplicated (same names from both instruments result in keeping only one) |
| 662 | + self.assertEqual(len(combined.components), len(inst1.components)) |
660 | 663 |
|
661 | 664 | # Check that connections are combined |
662 | 665 | self.assertEqual(len(combined.connections), len(inst1.connections) + len(inst2.connections)) |
@@ -710,6 +713,128 @@ def test_instrument_addition(self): |
710 | 713 | combined = inst1 + inst2 |
711 | 714 | self.assertEqual(combined.notes, "Only note") |
712 | 715 |
|
| 716 | + def test_duplicate_non_harp_device_components(self): |
| 717 | + """Test that duplicate non-HarpDevice components log an error when combining instruments""" |
| 718 | + |
| 719 | + inst1 = Instrument( |
| 720 | + instrument_id="test_inst", |
| 721 | + modification_date=date(2020, 10, 10), |
| 722 | + modalities=[Modality.ECEPHYS], |
| 723 | + coordinate_system=CoordinateSystemLibrary.BREGMA_ARI, |
| 724 | + components=[Computer(name="Computer1")], |
| 725 | + ) |
| 726 | + inst2 = Instrument( |
| 727 | + instrument_id="test_inst", |
| 728 | + modification_date=date(2020, 10, 10), |
| 729 | + modalities=[Modality.ECEPHYS], |
| 730 | + coordinate_system=CoordinateSystemLibrary.BREGMA_ARI, |
| 731 | + components=[Computer(name="Computer1")], |
| 732 | + ) |
| 733 | + |
| 734 | + with patch("aind_data_schema.core.instrument.logging") as mock_logging: |
| 735 | + combined = inst1 + inst2 |
| 736 | + mock_logging.error.assert_called_once() |
| 737 | + error_call_args = mock_logging.error.call_args[0][0] |
| 738 | + self.assertIn("Computer1", error_call_args) |
| 739 | + self.assertIn("duplicated", error_call_args) |
| 740 | + |
| 741 | + self.assertEqual(len(combined.components), 1) |
| 742 | + |
| 743 | + def test_duplicate_harp_clock_generator_devices(self): |
| 744 | + """Test that duplicate HarpDevice clock generators are allowed when combining instruments""" |
| 745 | + |
| 746 | + harp_clock_gen = HarpDevice( |
| 747 | + name="Harp Clock Generator", |
| 748 | + harp_device_type=HarpDeviceType.CLOCKSYNCHRONIZER, |
| 749 | + core_version="2.1", |
| 750 | + channels=[], |
| 751 | + is_clock_generator=True, |
| 752 | + ) |
| 753 | + |
| 754 | + inst1 = Instrument( |
| 755 | + instrument_id="test_inst", |
| 756 | + modification_date=date(2020, 10, 10), |
| 757 | + modalities=[Modality.ECEPHYS], |
| 758 | + coordinate_system=CoordinateSystemLibrary.BREGMA_ARI, |
| 759 | + components=[harp_clock_gen], |
| 760 | + ) |
| 761 | + inst2 = Instrument( |
| 762 | + instrument_id="test_inst", |
| 763 | + modification_date=date(2020, 10, 10), |
| 764 | + modalities=[Modality.ECEPHYS], |
| 765 | + coordinate_system=CoordinateSystemLibrary.BREGMA_ARI, |
| 766 | + components=[harp_clock_gen.model_copy(deep=True)], |
| 767 | + ) |
| 768 | + |
| 769 | + with patch("aind_data_schema.core.instrument.logging") as mock_logging: |
| 770 | + combined = inst1 + inst2 |
| 771 | + mock_logging.info.assert_called_once() |
| 772 | + info_call_args = mock_logging.info.call_args[0][0] |
| 773 | + self.assertIn("Harp Clock Generator", info_call_args) |
| 774 | + |
| 775 | + self.assertEqual(len(combined.components), 1) |
| 776 | + |
| 777 | + def test_duplicate_non_harp_device_with_clock_generator_attribute(self): |
| 778 | + """Test that duplicate non-HarpDevice components with is_clock_generator log error""" |
| 779 | + |
| 780 | + harp_clock_gen = HarpDevice( |
| 781 | + name="CustomClockGenerator", |
| 782 | + harp_device_type=HarpDeviceType.BEHAVIOR, |
| 783 | + is_clock_generator=True, |
| 784 | + channels=[], |
| 785 | + ) |
| 786 | + |
| 787 | + harp_non_clock_gen = HarpDevice( |
| 788 | + name="CustomClockGenerator", |
| 789 | + harp_device_type=HarpDeviceType.BEHAVIOR, |
| 790 | + is_clock_generator=False, |
| 791 | + channels=[], |
| 792 | + ) |
| 793 | + |
| 794 | + inst1 = Instrument( |
| 795 | + instrument_id="test_inst", |
| 796 | + modification_date=date(2020, 10, 10), |
| 797 | + modalities=[Modality.BEHAVIOR], |
| 798 | + coordinate_system=CoordinateSystemLibrary.BREGMA_ARI, |
| 799 | + components=[harp_clock_gen, LickSpoutAssembly( |
| 800 | + name="Lick spout assembly A", |
| 801 | + lick_spouts=[ |
| 802 | + LickSpout( |
| 803 | + name="Left spout", |
| 804 | + spout_diameter=1.2, |
| 805 | + solenoid_valve=Device(name="Solenoid Left"), |
| 806 | + lick_sensor=Device(name="Lick-o-meter Left"), |
| 807 | + ), |
| 808 | + ], |
| 809 | + )], |
| 810 | + ) |
| 811 | + inst2 = Instrument( |
| 812 | + instrument_id="test_inst", |
| 813 | + modification_date=date(2020, 10, 10), |
| 814 | + modalities=[Modality.BEHAVIOR], |
| 815 | + coordinate_system=CoordinateSystemLibrary.BREGMA_ARI, |
| 816 | + components=[harp_non_clock_gen, LickSpoutAssembly( |
| 817 | + name="Lick spout assembly B", |
| 818 | + lick_spouts=[ |
| 819 | + LickSpout( |
| 820 | + name="Left spout", |
| 821 | + spout_diameter=1.2, |
| 822 | + solenoid_valve=Device(name="Solenoid Left"), |
| 823 | + lick_sensor=Device(name="Lick-o-meter Left"), |
| 824 | + ), |
| 825 | + ], |
| 826 | + )], |
| 827 | + ) |
| 828 | + |
| 829 | + with patch("aind_data_schema.core.instrument.logging") as mock_logging: |
| 830 | + combined = inst1 + inst2 |
| 831 | + mock_logging.error.assert_called_once() |
| 832 | + error_call_args = mock_logging.error.call_args[0][0] |
| 833 | + self.assertIn("CustomClockGenerator", error_call_args) |
| 834 | + self.assertIn("duplicated", error_call_args) |
| 835 | + |
| 836 | + self.assertEqual(len(combined.components), 3) |
| 837 | + |
713 | 838 |
|
714 | 839 | class ConnectionTest(unittest.TestCase): |
715 | 840 | """Test the Connection schema""" |
|
0 commit comments