added infrastructure code
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""Integration tests for AnnotationRepositoryInterface."""
|
||||
|
||||
from uuid import UUID
|
||||
import pytest
|
||||
from data_store.interfaces import AnnotationRepositoryInterface
|
||||
from data_store.dto import Annotation
|
||||
|
||||
|
||||
def test_get_raises_not_implemented_if_not_overwritten(
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling get on a subclass without get implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(AnnotationRepositoryInterface):
|
||||
"""A class missing the get method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Annotation | None:
|
||||
return super().get(job_id) # type: ignore
|
||||
|
||||
def put(self, annotation: Annotation, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.get(job_id_list[0])
|
||||
|
||||
|
||||
def test_put_raises_not_implemented_if_not_overwritten(
|
||||
annotation: Annotation,
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling put on a subclass without put implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(AnnotationRepositoryInterface):
|
||||
"""A class missing the put method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Annotation | None:
|
||||
return None
|
||||
|
||||
def put(self, annotation: Annotation, job_id: UUID) -> None:
|
||||
return super().put(annotation, job_id) # type: ignore
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.put(annotation, job_id_list[0])
|
||||
|
||||
|
||||
def test_delete_raises_not_implemented_if_not_overwritten(
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling delete on a subclass without delete implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(AnnotationRepositoryInterface):
|
||||
"""A class missing the delete method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Annotation | None:
|
||||
return None
|
||||
|
||||
def put(self, annotation: Annotation, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return super().delete(job_id) # type: ignore
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.delete(job_id_list[0])
|
||||
|
||||
|
||||
def test_list_all_raises_not_implemented_if_not_overwritten() -> None:
|
||||
"""Test that calling list_all on a subclass without list_all implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(AnnotationRepositoryInterface):
|
||||
"""A class missing the list_all method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Annotation | None:
|
||||
return None
|
||||
|
||||
def put(self, annotation: Annotation, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return super().list_all() # type: ignore
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.list_all()
|
||||
|
||||
|
||||
# Allows local debugging by running file as script
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-s", "-v", __file__])
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Integration tests for AnnotationRepository."""
|
||||
|
||||
from uuid import UUID
|
||||
import pytest
|
||||
from data_store.repositories.annotation_repository import AnnotationRepository
|
||||
from data_store.dto import (
|
||||
Annotation,
|
||||
)
|
||||
|
||||
|
||||
def test_get_annotation(
|
||||
annotation_in_minio: tuple[UUID, Annotation],
|
||||
annotation_repository: AnnotationRepository,
|
||||
) -> None:
|
||||
"""Test retrieving an annotation from AnnotationRepository."""
|
||||
# Arrange
|
||||
job_id, expected_annotation = annotation_in_minio
|
||||
# Act
|
||||
retrieved_annotation = annotation_repository.get(job_id)
|
||||
# Assert
|
||||
assert retrieved_annotation is not None
|
||||
assert isinstance(retrieved_annotation, Annotation)
|
||||
assert retrieved_annotation == expected_annotation
|
||||
|
||||
|
||||
def test_put_annotation(
|
||||
annotation_repository: AnnotationRepository,
|
||||
annotation: Annotation,
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test storing an annotation in AnnotationRepository."""
|
||||
# Arrange
|
||||
job_id = job_id_list[0]
|
||||
assert annotation_repository.get(job_id) is None
|
||||
# Act
|
||||
annotation_repository.put(annotation, job_id)
|
||||
# Assert
|
||||
received_annotation = annotation_repository.get(job_id)
|
||||
assert received_annotation is not None
|
||||
assert isinstance(received_annotation, Annotation)
|
||||
assert received_annotation == annotation
|
||||
|
||||
|
||||
def test_delete_annotation(
|
||||
annotation_in_minio: tuple[UUID, Annotation],
|
||||
annotation_repository: AnnotationRepository,
|
||||
) -> None:
|
||||
"""Test deleting an annotation from AnnotationRepository."""
|
||||
# Arrange
|
||||
job_id, _ = annotation_in_minio
|
||||
assert annotation_repository.get(job_id) is not None
|
||||
# Act
|
||||
annotation_repository.delete(job_id)
|
||||
# Assert
|
||||
assert annotation_repository.get(job_id) is None
|
||||
|
||||
|
||||
def test_list_all_annotations(
|
||||
annotation_in_minio: tuple[UUID, Annotation],
|
||||
annotation_repository: AnnotationRepository,
|
||||
) -> None:
|
||||
"""Test listing all annotations in AnnotationRepository."""
|
||||
# Arrange
|
||||
job_id, _ = annotation_in_minio
|
||||
# Act
|
||||
annotation_ids = annotation_repository.list_all()
|
||||
# Assert
|
||||
assert isinstance(annotation_ids, list)
|
||||
assert all(isinstance(i, UUID) for i in annotation_ids)
|
||||
assert job_id in annotation_ids
|
||||
|
||||
|
||||
def test_list_all_annotations_empty(
|
||||
annotation_repository: AnnotationRepository,
|
||||
) -> None:
|
||||
"""Test listing all annotations in an empty AnnotationRepository."""
|
||||
# Act
|
||||
annotation_ids = annotation_repository.list_all()
|
||||
# Assert
|
||||
assert isinstance(annotation_ids, list)
|
||||
assert len(annotation_ids) == 0
|
||||
|
||||
|
||||
# Allows local debugging by running file as script
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-s", "-v", __file__])
|
||||
@@ -0,0 +1,340 @@
|
||||
"""Integration test configuration."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import json
|
||||
from collections.abc import Generator
|
||||
from uuid import UUID
|
||||
from io import BytesIO
|
||||
import pandas as pd
|
||||
from PIL import Image
|
||||
import pytest
|
||||
import structlog
|
||||
from minio import Minio, S3Error
|
||||
from testcontainers.minio import MinioContainer
|
||||
from python_repositories.adapters import MinioAdapter
|
||||
from data_store.repositories import (
|
||||
ImageRepository,
|
||||
AnnotationRepository,
|
||||
JobRepository,
|
||||
)
|
||||
from data_store.dto import (
|
||||
Annotation,
|
||||
DistanceEnum,
|
||||
AngleEnum,
|
||||
PointOfViewEnum,
|
||||
ContactEnum,
|
||||
Job,
|
||||
)
|
||||
|
||||
# Last Docker image with a useful WebUI
|
||||
MINIO_DOCKER_IMAGE = "minio/minio:RELEASE.2023-03-20T20-16-18Z"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def configure_logging() -> None:
|
||||
"""Configure logging for the test session."""
|
||||
# Configure structlog
|
||||
structlog.configure(
|
||||
processors=[
|
||||
structlog.stdlib.filter_by_level,
|
||||
structlog.stdlib.add_logger_name,
|
||||
structlog.stdlib.add_log_level,
|
||||
structlog.processors.TimeStamper(fmt="iso"),
|
||||
structlog.processors.JSONRenderer(),
|
||||
],
|
||||
logger_factory=structlog.stdlib.LoggerFactory(),
|
||||
wrapper_class=structlog.stdlib.BoundLogger,
|
||||
cache_logger_on_first_use=True,
|
||||
)
|
||||
# Set up basic logging configuration
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def minio_container() -> Generator[dict[str, str]]:
|
||||
"""Set up MinIO container and provide credentials for testing."""
|
||||
# Set variables
|
||||
access_key = "minioadmin"
|
||||
secret_key = "minioadmin"
|
||||
bucket_name = "test-bucket"
|
||||
# Start container
|
||||
container = MinioContainer(
|
||||
image=MINIO_DOCKER_IMAGE,
|
||||
access_key=access_key,
|
||||
secret_key=secret_key,
|
||||
).with_bind_ports( # expose webUI on 9001
|
||||
container=9001,
|
||||
host=9001,
|
||||
)
|
||||
container.start()
|
||||
# Build environment variables dictionary
|
||||
minio_host = container.get_container_host_ip()
|
||||
minio_port = container.get_exposed_port(9000)
|
||||
minio_endpoint = f"{minio_host}:{minio_port}"
|
||||
env_vars = {
|
||||
"MINIO_ENDPOINT": minio_endpoint,
|
||||
"MINIO_ACCESS_KEY": access_key,
|
||||
"MINIO_SECRET_KEY": secret_key,
|
||||
"MINIO_BUCKET": bucket_name,
|
||||
}
|
||||
|
||||
yield env_vars
|
||||
|
||||
# Stop container
|
||||
container.stop()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def set_environment_variables(
|
||||
minio_container: dict[str, str],
|
||||
) -> Generator[dict[str, str]]:
|
||||
"""Set environment variables needed for tests."""
|
||||
# Build environment variables dictionary
|
||||
env_vars = {}
|
||||
env_vars.update(minio_container)
|
||||
# Set environment variables
|
||||
for key, value in env_vars.items():
|
||||
os.environ[key] = value
|
||||
|
||||
yield env_vars
|
||||
|
||||
# Cleanup
|
||||
for key in env_vars:
|
||||
_ = os.environ.pop(key, default=None)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def job_id_list() -> Generator[list[UUID]]:
|
||||
"""Provide a list of job IDs for testing."""
|
||||
yield [
|
||||
UUID("d358cbba-75be-4c38-becd-7abf338ca941"),
|
||||
UUID("56d20d25-b733-4ed3-880c-732d7257dcaa"),
|
||||
UUID("8019292f-8353-43d7-a189-076c6630de7b"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def csv_data_file(
|
||||
job_id_list: list[UUID],
|
||||
) -> Generator[Path]:
|
||||
"""Create a CSV data file for testing."""
|
||||
# Create csv content
|
||||
csv_data = pd.DataFrame(
|
||||
{
|
||||
"job_id": job_id_list,
|
||||
"distance": [
|
||||
"close",
|
||||
"long",
|
||||
"wrong",
|
||||
],
|
||||
"angle": ["high", "low", "wrong"],
|
||||
"point_of_view": ["frontal", "oblique", "wrong"],
|
||||
"contact": ["offer", "demand", "wrong"],
|
||||
}
|
||||
)
|
||||
# Create data directory and file path
|
||||
data_dir = Path(tempfile.mkdtemp())
|
||||
data_path = data_dir / "annotations.csv"
|
||||
# Write CSV content to file
|
||||
csv_data.to_csv(data_path, header=True, index=False)
|
||||
|
||||
yield data_path
|
||||
|
||||
# Cleanup
|
||||
shutil.rmtree(data_dir)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def raw_minio_client(
|
||||
set_environment_variables: dict[str, str],
|
||||
) -> Generator[Minio]:
|
||||
"""Provide a MinIO client for testing."""
|
||||
# Create MinIO client
|
||||
client = Minio(
|
||||
endpoint=set_environment_variables["MINIO_ENDPOINT"],
|
||||
access_key=set_environment_variables["MINIO_ACCESS_KEY"],
|
||||
secret_key=set_environment_variables["MINIO_SECRET_KEY"],
|
||||
secure=False,
|
||||
)
|
||||
# Create bucket if it doesn't exist
|
||||
bucket_name = set_environment_variables["MINIO_BUCKET"]
|
||||
try:
|
||||
if not client.bucket_exists(bucket_name):
|
||||
client.make_bucket(bucket_name)
|
||||
except S3Error as e:
|
||||
raise RuntimeError(f"Failed to create or access bucket: {e}")
|
||||
|
||||
yield client
|
||||
|
||||
# No explicit cleanup needed for MinIO client
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def image_repository() -> Generator[ImageRepository]:
|
||||
"""Provide an ImageRepository instance for testing."""
|
||||
# Instantiate and connect repository
|
||||
repo = ImageRepository()
|
||||
repo.connect()
|
||||
|
||||
yield repo
|
||||
|
||||
# Cleanup
|
||||
repo.disconnect()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def image() -> Generator[Image.Image]:
|
||||
"""Provide a sample image for testing."""
|
||||
img = Image.new("RGB", (10, 10))
|
||||
yield img
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def image_in_minio(
|
||||
set_environment_variables: dict[str, str],
|
||||
raw_minio_client: Minio,
|
||||
image: Image.Image,
|
||||
job_id_list: list[UUID],
|
||||
) -> Generator[tuple[UUID, Image.Image]]:
|
||||
"""Upload a sample image to MinIO and provide its ID for testing."""
|
||||
# Prepare object name and buffer
|
||||
bucket_name = set_environment_variables["MINIO_BUCKET"]
|
||||
image_id = job_id_list[0]
|
||||
object_name = ImageRepository._object_name(image_id)
|
||||
buffer = BytesIO()
|
||||
image.save(buffer, ImageRepository.image_format)
|
||||
num_bytes = buffer.getbuffer().nbytes
|
||||
buffer.seek(0)
|
||||
# Upload image to MinIO
|
||||
raw_minio_client.put_object(
|
||||
bucket_name=bucket_name,
|
||||
object_name=object_name,
|
||||
data=buffer,
|
||||
length=num_bytes,
|
||||
part_size=MinioAdapter.chunk_size,
|
||||
)
|
||||
buffer.close()
|
||||
|
||||
yield image_id, image
|
||||
|
||||
# Cleanup: remove image from MinIO
|
||||
try:
|
||||
raw_minio_client.remove_object(
|
||||
bucket_name=bucket_name,
|
||||
object_name=object_name,
|
||||
)
|
||||
except S3Error as e:
|
||||
raise RuntimeError(f"Failed to remove object from bucket: {e}")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def annotation_repository() -> Generator[AnnotationRepository]:
|
||||
"""Provide an AnnotationRepository instance for testing."""
|
||||
# Instantiate and connect repository
|
||||
repo = AnnotationRepository()
|
||||
repo.connect()
|
||||
|
||||
yield repo
|
||||
|
||||
# Cleanup
|
||||
repo.disconnect()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def annotation() -> Generator[Annotation]:
|
||||
"""Provide a sample annotation for testing."""
|
||||
annotation = Annotation(
|
||||
distance=DistanceEnum.CLOSE,
|
||||
angle=AngleEnum.HIGH,
|
||||
point_of_view=PointOfViewEnum.FRONTAL,
|
||||
contact=ContactEnum.OFFER,
|
||||
)
|
||||
yield annotation
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def annotation_in_minio(
|
||||
set_environment_variables: dict[str, str],
|
||||
raw_minio_client: Minio,
|
||||
annotation: Annotation,
|
||||
job_id_list: list[UUID],
|
||||
) -> Generator[tuple[UUID, Annotation]]:
|
||||
"""Upload a sample annotation to MinIO and provide its ID for testing."""
|
||||
# Prepare object name and buffer
|
||||
bucket_name = set_environment_variables["MINIO_BUCKET"]
|
||||
job_id = job_id_list[0]
|
||||
object_name = AnnotationRepository._object_name(job_id)
|
||||
data_json = annotation.model_dump(mode="json")
|
||||
data_bytes = json.dumps(data_json).encode(AnnotationRepository.encoding)
|
||||
buffer = BytesIO(data_bytes)
|
||||
num_bytes = buffer.getbuffer().nbytes
|
||||
buffer.seek(0)
|
||||
# Upload annotation to MinIO
|
||||
raw_minio_client.put_object(
|
||||
bucket_name=bucket_name,
|
||||
object_name=object_name,
|
||||
data=buffer,
|
||||
length=num_bytes,
|
||||
part_size=MinioAdapter.chunk_size,
|
||||
)
|
||||
buffer.close()
|
||||
|
||||
yield job_id, annotation
|
||||
|
||||
# Cleanup: remove annotation from MinIO
|
||||
try:
|
||||
raw_minio_client.remove_object(
|
||||
bucket_name=bucket_name,
|
||||
object_name=object_name,
|
||||
)
|
||||
except S3Error as e:
|
||||
raise RuntimeError(f"Failed to remove object from bucket: {e}")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def job_repository() -> Generator[JobRepository]:
|
||||
"""Provide a JobRepository instance for testing."""
|
||||
# Instantiate and connect repository
|
||||
repo = JobRepository()
|
||||
repo.connect()
|
||||
|
||||
yield repo
|
||||
|
||||
# Cleanup
|
||||
repo.disconnect()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def job(
|
||||
job_id_list: list[UUID],
|
||||
image: Image.Image,
|
||||
annotation: Annotation,
|
||||
) -> Generator[Job]:
|
||||
"""Provide a sample job for testing."""
|
||||
job = Job(
|
||||
job_id=job_id_list[0],
|
||||
image=image,
|
||||
annotation=annotation,
|
||||
)
|
||||
yield job
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def job_in_minio(
|
||||
job: Job,
|
||||
image_in_minio: tuple[UUID, Image.Image],
|
||||
annotation_in_minio: tuple[UUID, Annotation],
|
||||
) -> Generator[Job]:
|
||||
"""Provide a sample job with its components already in MinIO for testing."""
|
||||
# A job consists of an image and an annotation and both must be in MinIO
|
||||
# before the job can be considered to be in MinIO.
|
||||
# Ensure the job_id matches those of the image and annotation
|
||||
image_id, _ = image_in_minio
|
||||
annotation_id, _ = annotation_in_minio
|
||||
assert job.job_id == annotation_id
|
||||
assert job.job_id == image_id
|
||||
yield job
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Integration tests for ImageRepositoryInterface."""
|
||||
|
||||
from uuid import UUID
|
||||
import pytest
|
||||
from PIL import Image
|
||||
from data_store.interfaces import ImageRepositoryInterface
|
||||
|
||||
|
||||
def test_get_raises_not_implemented_if_not_overwritten(
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling get on a subclass without get implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(ImageRepositoryInterface):
|
||||
"""A class missing the get method."""
|
||||
|
||||
def get(self, image_id: UUID) -> Image.Image | None:
|
||||
return super().get(image_id) # type: ignore
|
||||
|
||||
def put(self, image: Image.Image, image_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, image_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.get(job_id_list[0])
|
||||
|
||||
|
||||
def test_put_raises_not_implemented_if_not_overwritten(
|
||||
image: Image.Image,
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling put on a subclass without put implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(ImageRepositoryInterface):
|
||||
"""A class missing the put method."""
|
||||
|
||||
def get(self, image_id: UUID) -> Image.Image | None:
|
||||
return None
|
||||
|
||||
def put(self, image: Image.Image, image_id: UUID) -> None:
|
||||
return super().put(image, image_id) # type: ignore
|
||||
|
||||
def delete(self, image_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.put(image, job_id_list[0])
|
||||
|
||||
|
||||
def test_delete_raises_not_implemented_if_not_overwritten(
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling delete on a subclass without delete implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(ImageRepositoryInterface):
|
||||
"""A class missing the delete method."""
|
||||
|
||||
def get(self, image_id: UUID) -> Image.Image | None:
|
||||
return None
|
||||
|
||||
def put(self, image: Image.Image, image_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, image_id: UUID) -> None:
|
||||
return super().delete(image_id) # type: ignore
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.delete(job_id_list[0])
|
||||
|
||||
|
||||
def test_list_all_raises_not_implemented_if_not_overwritten() -> None:
|
||||
"""Test that calling list_all on a subclass without list_all implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(ImageRepositoryInterface):
|
||||
"""A class missing the list_all method."""
|
||||
|
||||
def get(self, image_id: UUID) -> Image.Image | None:
|
||||
return None
|
||||
|
||||
def put(self, image: Image.Image, image_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, image_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return super().list_all() # type: ignore
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.list_all()
|
||||
|
||||
|
||||
# Allows local debugging by running file as script
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-s", "-v", __file__])
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Integration tests for ImageRepository."""
|
||||
|
||||
from uuid import UUID
|
||||
import pytest
|
||||
from PIL import Image
|
||||
from data_store.repositories.image_repository import ImageRepository
|
||||
|
||||
|
||||
def same_image(img1: Image.Image, img2: Image.Image) -> bool:
|
||||
"""Check if two images are the same based on their attributes."""
|
||||
return (
|
||||
img1.size == img2.size
|
||||
and img1.mode == img2.mode
|
||||
and list(img1.getdata()) == list(img2.getdata())
|
||||
)
|
||||
|
||||
|
||||
def test_get_image(
|
||||
image_in_minio: tuple[UUID, Image.Image],
|
||||
image_repository: ImageRepository,
|
||||
) -> None:
|
||||
"""Test retrieving an image from ImageRepository."""
|
||||
# Arrange
|
||||
image_id, expected_image = image_in_minio
|
||||
# Act
|
||||
retrieved_image = image_repository.get(image_id)
|
||||
# Assert
|
||||
assert retrieved_image is not None
|
||||
assert isinstance(retrieved_image, Image.Image)
|
||||
assert same_image(retrieved_image, expected_image)
|
||||
|
||||
|
||||
def test_put_image(
|
||||
image_repository: ImageRepository,
|
||||
image: Image.Image,
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test storing an image in ImageRepository."""
|
||||
# Arrange
|
||||
image_id = job_id_list[0]
|
||||
assert image_repository.get(image_id) is None
|
||||
# Act
|
||||
image_repository.put(image, image_id)
|
||||
# Assert
|
||||
received_image = image_repository.get(image_id)
|
||||
assert received_image is not None
|
||||
assert isinstance(received_image, Image.Image)
|
||||
assert received_image.size == image.size
|
||||
assert received_image.mode == image.mode
|
||||
assert list(received_image.getdata()) == list(image.getdata())
|
||||
|
||||
|
||||
def test_delete_image(
|
||||
image_in_minio: tuple[UUID, Image.Image],
|
||||
image_repository: ImageRepository,
|
||||
) -> None:
|
||||
"""Test deleting an image from ImageRepository."""
|
||||
# Arrange
|
||||
image_id, _ = image_in_minio
|
||||
assert image_repository.get(image_id) is not None
|
||||
# Act
|
||||
image_repository.delete(image_id)
|
||||
# Assert
|
||||
assert image_repository.get(image_id) is None
|
||||
|
||||
|
||||
def test_list_all_images(
|
||||
image_in_minio: tuple[UUID, Image.Image],
|
||||
image_repository: ImageRepository,
|
||||
) -> None:
|
||||
"""Test listing all images in ImageRepository."""
|
||||
# Arrange
|
||||
image_id, _ = image_in_minio
|
||||
# Act
|
||||
image_ids = image_repository.list_all()
|
||||
# Assert
|
||||
assert isinstance(image_ids, list)
|
||||
assert all(isinstance(i, UUID) for i in image_ids)
|
||||
assert image_id in image_ids
|
||||
|
||||
|
||||
def test_list_all_images_empty(
|
||||
image_repository: ImageRepository,
|
||||
) -> None:
|
||||
"""Test listing all images in an empty ImageRepository."""
|
||||
# Act
|
||||
image_ids = image_repository.list_all()
|
||||
# Assert
|
||||
assert isinstance(image_ids, list)
|
||||
assert len(image_ids) == 0
|
||||
|
||||
|
||||
# Allows local debugging by running file as script
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-s", "-v", __file__])
|
||||
@@ -0,0 +1,109 @@
|
||||
"""Integration tests for the JobRepositoryInterface."""
|
||||
|
||||
from uuid import UUID
|
||||
import pytest
|
||||
from data_store.interfaces import JobRepositoryInterface
|
||||
from data_store.dto import Job
|
||||
|
||||
|
||||
def test_get_raises_not_implemented_if_not_overwritten(
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling get on a subclass without get implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(JobRepositoryInterface):
|
||||
"""A class missing the get method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Job | None:
|
||||
return super().get(job_id) # type: ignore
|
||||
|
||||
def put(self, job: Job) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.get(job_id_list[0])
|
||||
|
||||
|
||||
def test_put_raises_not_implemented_if_not_overwritten(
|
||||
job: Job,
|
||||
) -> None:
|
||||
"""Test that calling put on a subclass without put implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(JobRepositoryInterface):
|
||||
"""A class missing the put method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Job | None:
|
||||
return None
|
||||
|
||||
def put(self, job: Job) -> None:
|
||||
return super().put(job) # type: ignore
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.put(job)
|
||||
|
||||
|
||||
def test_delete_raises_not_implemented_if_not_overwritten(
|
||||
job_id_list: list[UUID],
|
||||
) -> None:
|
||||
"""Test that calling delete on a subclass without delete implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(JobRepositoryInterface):
|
||||
"""A class missing the delete method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Job | None:
|
||||
return None
|
||||
|
||||
def put(self, job: Job) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return super().delete(job_id) # type: ignore
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return []
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.delete(job_id_list[0])
|
||||
|
||||
|
||||
def test_list_all_raises_not_implemented_if_not_overwritten() -> None:
|
||||
"""Test that calling list_all on a subclass without list_all implemented raises NotImplementedError."""
|
||||
|
||||
class Incomplete(JobRepositoryInterface):
|
||||
"""A class missing the list_all method."""
|
||||
|
||||
def get(self, job_id: UUID) -> Job | None:
|
||||
return None
|
||||
|
||||
def put(self, job: Job) -> None:
|
||||
return None
|
||||
|
||||
def delete(self, job_id: UUID) -> None:
|
||||
return None
|
||||
|
||||
def list_all(self) -> list[UUID]:
|
||||
return super().list_all() # type: ignore
|
||||
|
||||
instance = Incomplete()
|
||||
with pytest.raises(NotImplementedError):
|
||||
instance.list_all()
|
||||
|
||||
|
||||
# Allows local debugging by running file as script
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-s", "-v", __file__])
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Integration tests for JobRepository."""
|
||||
|
||||
from uuid import UUID
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from data_store.repositories.job_repository import JobRepository
|
||||
from data_store.dto import Job
|
||||
|
||||
|
||||
def same_image(img1: Image.Image, img2: Image.Image) -> bool:
|
||||
"""Check if two images are the same based on their attributes."""
|
||||
assert img1.size == img2.size
|
||||
assert img1.mode == img2.mode
|
||||
assert list(img1.getdata()) == list(img2.getdata())
|
||||
return True
|
||||
|
||||
|
||||
def same_job(job1: Job, job2: Job) -> bool:
|
||||
"""Check if two Job instances are the same based on their attributes."""
|
||||
assert job1.job_id == job2.job_id
|
||||
assert same_image(job1.image, job2.image)
|
||||
assert job1.annotation == job2.annotation
|
||||
return True
|
||||
|
||||
|
||||
def test_get_job(
|
||||
job_in_minio: Job,
|
||||
job_repository: JobRepository,
|
||||
) -> None:
|
||||
"""Test retrieving a job from JobRepository."""
|
||||
# Arrange
|
||||
job_id = job_in_minio.job_id
|
||||
# Act
|
||||
retrieved_job = job_repository.get(job_id)
|
||||
# Assert
|
||||
assert retrieved_job is not None
|
||||
assert isinstance(retrieved_job, Job)
|
||||
assert same_job(retrieved_job, job_in_minio)
|
||||
|
||||
|
||||
def test_get_job_without_image_returns_none(
|
||||
annotation_in_minio: tuple[UUID, object],
|
||||
job_repository: JobRepository,
|
||||
) -> None:
|
||||
"""Test retrieving a job without an image returns None."""
|
||||
# Arrange
|
||||
job_id, _ = annotation_in_minio
|
||||
# Act
|
||||
retrieved_job = job_repository.get(job_id)
|
||||
# Assert
|
||||
assert retrieved_job is None
|
||||
|
||||
|
||||
def test_get_job_without_annotation_returns_none(
|
||||
image_in_minio: tuple[UUID, object],
|
||||
job_repository: JobRepository,
|
||||
) -> None:
|
||||
"""Test retrieving a job without an annotation returns None."""
|
||||
# Arrange
|
||||
job_id, _ = image_in_minio
|
||||
# Act
|
||||
retrieved_job = job_repository.get(job_id)
|
||||
# Assert
|
||||
assert retrieved_job is None
|
||||
|
||||
|
||||
def test_put_job(
|
||||
job_repository: JobRepository,
|
||||
job: Job,
|
||||
) -> None:
|
||||
"""Test storing a job in JobRepository."""
|
||||
# Arrange
|
||||
job_id = job.job_id
|
||||
assert job_repository.get(job_id) is None
|
||||
# Act
|
||||
job_repository.put(job)
|
||||
# Assert
|
||||
received_job = job_repository.get(job_id)
|
||||
assert received_job is not None
|
||||
assert isinstance(received_job, Job)
|
||||
assert same_job(received_job, job)
|
||||
|
||||
|
||||
def test_delete_job(
|
||||
job_in_minio: Job,
|
||||
job_repository: JobRepository,
|
||||
) -> None:
|
||||
"""Test deleting a job from JobRepository."""
|
||||
# Arrange
|
||||
job_id = job_in_minio.job_id
|
||||
assert job_repository.get(job_id) is not None
|
||||
# Act
|
||||
job_repository.delete(job_id)
|
||||
# Assert
|
||||
assert job_repository.get(job_id) is None
|
||||
|
||||
|
||||
def test_list_all_jobs(
|
||||
job_in_minio: Job,
|
||||
job_repository: JobRepository,
|
||||
) -> None:
|
||||
"""Test listing all job IDs from JobRepository."""
|
||||
# Arrange
|
||||
job_id = job_in_minio.job_id
|
||||
# Act
|
||||
job_ids = job_repository.list_all()
|
||||
# Assert
|
||||
assert isinstance(job_ids, list)
|
||||
assert all(isinstance(jid, UUID) for jid in job_ids)
|
||||
assert job_id in job_ids
|
||||
|
||||
|
||||
# Allows local debugging by running file as script
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-s", "-v", __file__])
|
||||
Reference in New Issue
Block a user