"""Unit tests for AnnotationRepository class.""" import unittest from unittest.mock import patch, MagicMock, _patch_dict from uuid import UUID from data_store.repositories.annotation_repository import AnnotationRepository from data_store.dto import ( Annotation, AngleEnum, ColourEnum, ContactEnum, DepthEnum, DistanceEnum, LightingEnum, PointOfViewEnum, ) class TestAnnotationRepository(unittest.TestCase): """Unit tests for AnnotationRepository class.""" patcher: _patch_dict repo: AnnotationRepository job_id: UUID bad_job_id: str annotation: Annotation bad_annotation: dict @classmethod def setUpClass(cls) -> None: """Set up test case with a mocked MinioAdapter.""" # Patch environment variables for MinioAdapter cls.patcher = patch.dict( "os.environ", { "MINIO_ENDPOINT": "localhost:9000", "MINIO_ACCESS_KEY": "minioadmin", "MINIO_SECRET_KEY": "minioadmin", "MINIO_BUCKET": "test-bucket", }, ) cls.patcher.start() # Prepare AnnotationRepository cls.repo = AnnotationRepository() cls.repo._get = MagicMock() # type: ignore[method-assign] cls.repo._put = MagicMock() # type: ignore[method-assign] cls.repo._delete = MagicMock() # type: ignore[method-assign] # Prepare other test variables cls.job_id = UUID("d87022f8-4169-4d69-8512-bbd65cc1cb23") cls.bad_job_id = "not-a-uuid" cls.annotation = Annotation( angle=AngleEnum.HIGH, angle_uncertainty=True, colour=ColourEnum.HIGH, colour_uncertainty=False, contact=ContactEnum.OFFER, contact_uncertainty=True, depth=DepthEnum.LOW, depth_uncertainty=False, distance=DistanceEnum.CLOSE, distance_uncertainty=True, lighting=LightingEnum.MEDIUM, lighting_uncertainty=False, point_of_view=PointOfViewEnum.FRONTAL, point_of_view_uncertainty=True, ) cls.bad_annotation = {"not_an_annotation": "irrelevant value"} @classmethod def tearDownClass(cls) -> None: # Stop patching cls.patcher.stop() def setUp(self) -> None: """Set up each test with a fresh AnnotationRepository instance.""" self.repo._get.reset_mock() # type: ignore[attr-defined] self.repo._put.reset_mock() # type: ignore[attr-defined] self.repo._delete.reset_mock() # type: ignore[attr-defined] def test_should_adhere_to_interface(self) -> None: """Test that AnnotationRepository adheres to AnnotationRepositoryInterface.""" # Instantiation fails if interface not adhered to _ = AnnotationRepository() def test_should_have_encoding_set(self) -> None: """Test that AnnotationRepository has encoding when instantiated.""" self.assertTrue(hasattr(self.repo, "encoding")) def test_should_have_data_format_set(self) -> None: """Test that AnnotationRepository has data_format when instantiated.""" self.assertTrue(hasattr(self.repo, "data_format")) def test_should_have_folder_name_set(self) -> None: """Test that AnnotationRepository has folder_name when instantiated.""" self.assertTrue(hasattr(self.repo, "folder_name")) def test_object_name_invalid_job_id_raises_value_error(self) -> None: """Test that _object_name() raises ValueError for invalid job_id.""" with self.assertRaises(ValueError): _ = self.repo._object_name(self.bad_job_id) # type: ignore def test_get_invalid_job_id_raises_value_error(self) -> None: """Test that get() raises ValueError for invalid job_id.""" with self.assertRaises(ValueError): self.repo.get(self.bad_job_id) # type: ignore def test_get_data_that_cannot_be_converted_returns_none(self) -> None: """Test that get() returns None if retrieved data cannot be converted to Annotation.""" # Arrange bad_data = b'{"not":"valid annotation data"}' mock_buffer = MagicMock() mock_buffer.read.return_value = bad_data self.repo._get.return_value = mock_buffer # type: ignore[attr-defined] # Act result = self.repo.get(self.job_id) # Assert self.assertIsNone(result) self.repo._get.assert_called_once_with( # type: ignore[attr-defined] f"{self.repo.folder_name}/{self.job_id}.{self.repo.data_format}" ) def test_put_invalid_job_id_raises_value_error(self) -> None: """Test that put() raises ValueError for invalid job_id.""" with self.assertRaises(ValueError): self.repo.put(self.annotation, self.bad_job_id) # type: ignore def test_put_invalid_annotation_raises_value_error(self) -> None: """Test that put() raises ValueError for invalid annotation.""" with self.assertRaises(ValueError): self.repo.put(self.bad_annotation, self.job_id) # type: ignore def test_delete_invalid_job_id_raises_value_error(self) -> None: """Test that delete() raises ValueError for invalid job_id.""" with self.assertRaises(ValueError): self.repo.delete(self.bad_job_id) # type: ignore # Allows local debugging by running file as script if __name__ == "__main__": unittest.main()