added integration tests and repository pattern for image, model and visual communication DTOs
Code Quality Pipeline / Check Code (pull_request) Failing after 3m21s
Code Quality Pipeline / Check Code (pull_request) Failing after 3m21s
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
"""Integration tests configuration."""
|
||||
|
||||
import os
|
||||
import random
|
||||
from collections.abc import Iterator
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import minio
|
||||
import pytest
|
||||
from PIL import Image
|
||||
from pymongo import MongoClient
|
||||
from testcontainers.minio import MinioContainer
|
||||
from testcontainers.mongodb import MongoDbContainer
|
||||
|
||||
from model.src.models import VisualCommunicationModel
|
||||
from shared.repositories import (
|
||||
HexadecimalString,
|
||||
ImageData,
|
||||
ImageRepository,
|
||||
ModelData,
|
||||
ModelRepository,
|
||||
VisualCommunicationData,
|
||||
VisualCommunicationRepository,
|
||||
VisualCommunicationValues,
|
||||
)
|
||||
from shared.repositories.src.implementations import (
|
||||
MinioImplementation,
|
||||
MongoImplementation,
|
||||
)
|
||||
|
||||
# set random seed for reproducibility
|
||||
random.seed(13)
|
||||
|
||||
# define test static variables
|
||||
MINIO_PORT = 9000
|
||||
MINIO_ACCESS_KEY = 'minioadmin'
|
||||
MINIO_SECRET_KEY = 'minioadmin'
|
||||
MONGO_PORT = 27017
|
||||
MONGO_USERNAME = 'admin'
|
||||
MONGO_PASSWORD = 'password'
|
||||
|
||||
env_var_map = {
|
||||
'MINIO_ENDPOINT': f'localhost:{MINIO_PORT}', # updated when container is running
|
||||
'MINIO_ACCESS_KEY': MINIO_ACCESS_KEY,
|
||||
'MINIO_SECRET_KEY': MINIO_SECRET_KEY,
|
||||
'MINIO_BUCKET_NAME': 'test-bucket',
|
||||
'MINIO_OBJECT_NAME': 'test-object',
|
||||
'MINIO_IMAGE_NAME': 'test-image',
|
||||
'MONGO_ENDPOINT': (
|
||||
f'mongodb://{MONGO_USERNAME}:{MONGO_PASSWORD}' f'@localhost:{MONGO_PORT}/'
|
||||
), # updated when container is running
|
||||
'MONGO_DB': 'test-db',
|
||||
'MONGO_COLLECTION': 'test-collection',
|
||||
}
|
||||
container_map = {
|
||||
'minio': MinioContainer(
|
||||
port=MINIO_PORT,
|
||||
access_key=MINIO_ACCESS_KEY,
|
||||
secret_key=MINIO_SECRET_KEY,
|
||||
),
|
||||
'mongo': MongoDbContainer(
|
||||
port=MONGO_PORT,
|
||||
username=MONGO_USERNAME,
|
||||
password=MONGO_PASSWORD,
|
||||
dbname=env_var_map['MONGO_DB'],
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def setup_infrastructure(request: pytest.FixtureRequest) -> None:
|
||||
"""Prepare infrastructure for integration test."""
|
||||
# prepare infrastructure
|
||||
for container in container_map.values():
|
||||
container.start()
|
||||
# update env var map
|
||||
minio_host = container_map['minio'].get_container_host_ip()
|
||||
minio_port = container_map['minio'].get_exposed_port(MINIO_PORT)
|
||||
env_var_map['MINIO_ENDPOINT'] = f'{minio_host}:{minio_port}'
|
||||
mongo_host = container_map['mongo'].get_container_host_ip()
|
||||
mongo_port = container_map['mongo'].get_exposed_port(MONGO_PORT)
|
||||
env_var_map['MONGO_ENDPOINT'] = (
|
||||
f'mongodb://{MONGO_USERNAME}:{MONGO_PASSWORD}@{mongo_host}:{mongo_port}/'
|
||||
)
|
||||
|
||||
# ensure cleanup
|
||||
def cleanup_infrastructure():
|
||||
for container in container_map.values():
|
||||
container.stop()
|
||||
|
||||
request.addfinalizer(cleanup_infrastructure)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', autouse=True)
|
||||
def populate_env(
|
||||
request: pytest.FixtureRequest,
|
||||
setup_infrastructure,
|
||||
) -> None:
|
||||
"""Populate environment with variables used for testing."""
|
||||
# update env
|
||||
for key, val in env_var_map.items():
|
||||
os.environ[key] = val
|
||||
|
||||
# ensure cleanup
|
||||
def cleanup_env():
|
||||
for key in env_var_map:
|
||||
_ = os.environ.pop(key, default=None)
|
||||
|
||||
request.addfinalizer(cleanup_env)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def raw_minio_client(
|
||||
setup_infrastructure,
|
||||
populate_env,
|
||||
) -> Iterator[minio.Minio]:
|
||||
"""Raw Minio client fixture."""
|
||||
# prepare arguments
|
||||
minio_endpoint = str(os.getenv('MINIO_ENDPOINT'))
|
||||
minio_access_key = str(os.getenv('MINIO_ACCESS_KEY'))
|
||||
minio_secret_key = str(os.getenv('MINIO_SECRET_KEY'))
|
||||
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
|
||||
# connect client
|
||||
client = minio.Minio(
|
||||
endpoint=minio_endpoint,
|
||||
access_key=minio_access_key,
|
||||
secret_key=minio_secret_key,
|
||||
secure=False,
|
||||
)
|
||||
# ensure bucket exists
|
||||
if not client.bucket_exists(bucket_name=minio_bucket_name):
|
||||
client.make_bucket(bucket_name=minio_bucket_name)
|
||||
# expose client
|
||||
yield client
|
||||
# cleanup
|
||||
object_list = client.list_objects(minio_bucket_name, recursive=True)
|
||||
for obj in object_list:
|
||||
client.remove_object(
|
||||
bucket_name=obj.bucket_name,
|
||||
object_name=obj.object_name,
|
||||
)
|
||||
client.remove_bucket(minio_bucket_name)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def minio_client(
|
||||
setup_infrastructure,
|
||||
populate_env,
|
||||
) -> Iterator[MinioImplementation]:
|
||||
"""MinioImplementation fixture."""
|
||||
# instantiate and connect client
|
||||
minio_client = MinioImplementation()
|
||||
minio_client.connect()
|
||||
# expose client
|
||||
yield minio_client
|
||||
# cleanup
|
||||
object_name_list = minio_client._list_objects(Path('*'))
|
||||
for name in object_name_list:
|
||||
minio_client._delete(Path(name))
|
||||
minio_client.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def buffer() -> Iterator[BytesIO]:
|
||||
"""Bytes buffer fixture."""
|
||||
# generate reproducible random data
|
||||
data = random.randbytes(n=2**21) # 2 MB
|
||||
# convert data
|
||||
buffer = BytesIO(data)
|
||||
# expose buffer
|
||||
yield buffer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def buffer_in_minio(
|
||||
raw_minio_client: minio.Minio,
|
||||
buffer: BytesIO,
|
||||
) -> Iterator[tuple[Path, BytesIO]]:
|
||||
"""Buffer in Minio fixture."""
|
||||
# prepare arguments
|
||||
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
|
||||
minio_object_name = str(os.getenv('MINIO_OBJECT_NAME'))
|
||||
# prepare for saving
|
||||
num_bytes = len(buffer.getvalue())
|
||||
buffer.seek(0)
|
||||
# put data in bucket
|
||||
raw_minio_client.put_object(
|
||||
bucket_name=minio_bucket_name,
|
||||
object_name=minio_object_name,
|
||||
length=num_bytes,
|
||||
data=buffer,
|
||||
)
|
||||
# expose data
|
||||
yield Path(minio_object_name), buffer
|
||||
# cleanup
|
||||
raw_minio_client.remove_object(
|
||||
bucket_name=minio_bucket_name,
|
||||
object_name=minio_object_name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def image_data() -> Iterator[ImageData]:
|
||||
"""Image data fixture."""
|
||||
# prepare arguments
|
||||
name = str(os.getenv('MINIO_IMAGE_NAME'))
|
||||
image = Image.new(mode='RGB', size=(480, 480))
|
||||
# instantiate data
|
||||
image_data = ImageData(image=image, name=name)
|
||||
# expose data
|
||||
yield image_data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def image_data_in_minio(
|
||||
raw_minio_client: minio.Minio,
|
||||
image_data: ImageData,
|
||||
) -> Iterator[ImageData]:
|
||||
"""Image data in Minio fixture."""
|
||||
# prepare arguments
|
||||
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
|
||||
image_name = image_data.name
|
||||
# build object path
|
||||
object_path = ImageRepository._build_path(image_name)
|
||||
# save image to buffer
|
||||
buffer = BytesIO()
|
||||
image_data.image.save(buffer, 'png')
|
||||
# prepare for saving
|
||||
num_bytes = len(buffer.getvalue())
|
||||
buffer.seek(0)
|
||||
# put data in bucket
|
||||
raw_minio_client.put_object(
|
||||
bucket_name=minio_bucket_name,
|
||||
object_name=object_path.as_posix(),
|
||||
length=num_bytes,
|
||||
data=buffer,
|
||||
)
|
||||
# expose data
|
||||
yield image_data
|
||||
# cleanup
|
||||
raw_minio_client.remove_object(
|
||||
bucket_name=minio_bucket_name,
|
||||
object_name=object_path.as_posix(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def image_repo(
|
||||
setup_infrastructure,
|
||||
populate_env,
|
||||
) -> Iterator[ImageRepository]:
|
||||
"""Image repository fixture."""
|
||||
repo = ImageRepository()
|
||||
repo.connect()
|
||||
yield repo
|
||||
repo.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def model_data() -> Iterator[ModelData]:
|
||||
"""Model data fixture."""
|
||||
# prepare arguments
|
||||
vis_com_model = VisualCommunicationModel().to('cpu')
|
||||
class_name = type(vis_com_model).__name__
|
||||
buffer = ModelData.model_to_buffer(vis_com_model)
|
||||
buffer_checksum = HexadecimalString('77dcab1769563654a6e24f92d40f29bd')
|
||||
# instantiate data
|
||||
model_data = ModelData(
|
||||
buffer=buffer,
|
||||
buffer_checksum=buffer_checksum,
|
||||
class_name=class_name,
|
||||
)
|
||||
# expose model
|
||||
yield model_data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def model_data_in_minio(
|
||||
raw_minio_client: minio.Minio,
|
||||
model_data: ModelData,
|
||||
) -> Iterator[ModelData]:
|
||||
"""Model data in Minio fixture."""
|
||||
# prepare arguments
|
||||
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
|
||||
object_name = ModelRepository._build_object_name(model_data)
|
||||
buffer = model_data.buffer
|
||||
# build object path
|
||||
object_path = ModelRepository._prefix() / object_name
|
||||
# prepare for saving
|
||||
num_bytes = len(buffer.getvalue())
|
||||
buffer.seek(0)
|
||||
# put data in bucket
|
||||
raw_minio_client.put_object(
|
||||
bucket_name=minio_bucket_name,
|
||||
object_name=object_path.as_posix(),
|
||||
length=num_bytes,
|
||||
data=buffer,
|
||||
)
|
||||
# expose data
|
||||
yield model_data
|
||||
# cleanup
|
||||
raw_minio_client.remove_object(
|
||||
bucket_name=minio_bucket_name,
|
||||
object_name=object_path.as_posix(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def model_repo(
|
||||
setup_infrastructure,
|
||||
populate_env,
|
||||
) -> Iterator[ModelRepository]:
|
||||
"""Model repository fixture."""
|
||||
repo = ModelRepository()
|
||||
repo.connect()
|
||||
yield repo
|
||||
repo.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def raw_mongo_client(
|
||||
setup_infrastructure,
|
||||
populate_env,
|
||||
) -> Iterator[MongoClient]:
|
||||
"""Raw mongo client fixture."""
|
||||
# prepare arguments
|
||||
mongo_endpoint = str(os.getenv('MONGO_ENDPOINT'))
|
||||
mongo_database = str(os.getenv('MONGO_DB'))
|
||||
# connect client
|
||||
client = MongoClient(mongo_endpoint)
|
||||
_ = client[mongo_database]
|
||||
# expose client
|
||||
yield client
|
||||
# cleanup
|
||||
client.drop_database(mongo_database)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mongo_client(
|
||||
setup_infrastructure,
|
||||
populate_env,
|
||||
) -> Iterator[MongoImplementation]:
|
||||
"""MongoImplementation fixture."""
|
||||
# instantiate and connect client
|
||||
mongo_client = MongoImplementation()
|
||||
mongo_client.connect()
|
||||
# expose client
|
||||
yield mongo_client
|
||||
# cleanup
|
||||
mongo_client._collection.drop()
|
||||
mongo_client.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dictionary() -> Iterator[dict]:
|
||||
"""Dictionary fixture."""
|
||||
# prepare data
|
||||
data = {
|
||||
'name': 'test-dictionary',
|
||||
'str_key': 'value',
|
||||
'int_key': 100,
|
||||
'float_key': 3.14,
|
||||
'list_key': [1, 2, 3],
|
||||
'dict_key': {
|
||||
'nested_key': 'nested_value',
|
||||
},
|
||||
}
|
||||
# expose data
|
||||
yield data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dictionary_in_mongo(
|
||||
raw_mongo_client: MongoClient,
|
||||
dictionary: dict,
|
||||
) -> Iterator[dict]:
|
||||
"""Dictionary in Mongo fixture."""
|
||||
# prepare arguments
|
||||
database = str(os.getenv('MONGO_DB'))
|
||||
collection = str(os.getenv('MONGO_COLLECTION'))
|
||||
# save data
|
||||
_ = raw_mongo_client[database][collection].insert_one(dictionary.copy())
|
||||
# expose data
|
||||
yield dictionary
|
||||
# cleanup
|
||||
raw_mongo_client[database][collection].delete_one(dictionary)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def visual_communication_values() -> Iterator[VisualCommunicationValues]:
|
||||
"""Visual communication values fixture."""
|
||||
# instantiate with random values
|
||||
visual_communication_values = VisualCommunicationValues.from_random()
|
||||
# expose values
|
||||
yield visual_communication_values
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def visual_communication_data(
|
||||
visual_communication_values: VisualCommunicationValues,
|
||||
) -> Iterator[VisualCommunicationData]:
|
||||
"""Visual communication data fixture."""
|
||||
# prepare arguments
|
||||
name = 'test-visual-communication'
|
||||
annotation = visual_communication_values
|
||||
# instantiate data
|
||||
visual_communication_data = VisualCommunicationData(
|
||||
name=name,
|
||||
annotation=annotation,
|
||||
)
|
||||
# expose data
|
||||
yield visual_communication_data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def visual_communication_data_in_mongo(
|
||||
raw_mongo_client: MongoClient,
|
||||
visual_communication_data: VisualCommunicationData,
|
||||
) -> Iterator[VisualCommunicationData]:
|
||||
"""Visual communication data in Mongo fixture."""
|
||||
# prepare arguments
|
||||
database = str(os.getenv('MONGO_DB'))
|
||||
collection = str(os.getenv('MONGO_COLLECTION'))
|
||||
# convert data
|
||||
dictionary = visual_communication_data.model_dump(mode='dict')
|
||||
# save data
|
||||
_ = raw_mongo_client[database][collection].insert_one(dictionary.copy())
|
||||
# expose data
|
||||
yield visual_communication_data
|
||||
# cleanup
|
||||
raw_mongo_client[database][collection].delete_one(dictionary)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def visual_communication_repo(
|
||||
setup_infrastructure,
|
||||
populate_env,
|
||||
) -> Iterator[VisualCommunicationRepository]:
|
||||
"""Visual communication repository fixture."""
|
||||
repo = VisualCommunicationRepository()
|
||||
repo.connect()
|
||||
yield repo
|
||||
repo.close()
|
||||
@@ -0,0 +1,156 @@
|
||||
"""Integration tests for ImageRepository class."""
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from shared.repositories import ImageData, ImageRepository
|
||||
|
||||
|
||||
def same_image(
|
||||
img_a: Image.Image,
|
||||
img_b: Image.Image,
|
||||
) -> bool:
|
||||
"""Check if two images contain the same data."""
|
||||
assert isinstance(img_a, Image.Image)
|
||||
assert isinstance(img_b, Image.Image)
|
||||
# check if images have a comparable number of channels
|
||||
if img_a.getbands() != img_b.getbands():
|
||||
return False
|
||||
# calculate pixel difference between images
|
||||
img_a_arr = np.asarray(img_a)
|
||||
img_b_arr = np.asarray(img_b)
|
||||
diff = np.subtract(img_a_arr, img_b_arr)
|
||||
if np.sum(diff) != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def same_image_data(
|
||||
data_a: ImageData,
|
||||
data_b: ImageData,
|
||||
) -> bool:
|
||||
"""Check if two ImageData-objects contain the same data."""
|
||||
assert isinstance(data_a, ImageData)
|
||||
assert isinstance(data_b, ImageData)
|
||||
# compare names
|
||||
if data_a.name != data_b.name:
|
||||
return False
|
||||
# compare images
|
||||
if not same_image(data_a.image, data_b.image):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def test_should_have_context_handler():
|
||||
"""Test that class has a working context handler implemented."""
|
||||
# ACT
|
||||
with ImageRepository() as repo:
|
||||
# ASSERT
|
||||
assert repo.connected()
|
||||
|
||||
|
||||
def test_should_get_image_data(
|
||||
image_repo: ImageRepository,
|
||||
image_data_in_minio: ImageData,
|
||||
):
|
||||
"""Test getting image data."""
|
||||
# ARRANGE
|
||||
image_name = image_data_in_minio.name
|
||||
# ACT
|
||||
received_image_data = image_repo.get_data(image_name)
|
||||
# ASSERT
|
||||
assert received_image_data is not None
|
||||
assert isinstance(received_image_data, ImageData)
|
||||
assert same_image_data(image_data_in_minio, received_image_data)
|
||||
|
||||
|
||||
def test_should_get_none_when_no_image_data(
|
||||
image_repo: ImageRepository,
|
||||
image_data: ImageData,
|
||||
):
|
||||
"""Test getting None when no data is available."""
|
||||
# ARRANGE
|
||||
image_name = image_data.name
|
||||
# ACT
|
||||
received_image_data = image_repo.get_data(image_name)
|
||||
# ASSERT
|
||||
assert received_image_data is None
|
||||
|
||||
|
||||
def test_should_delete_image_data(
|
||||
image_repo: ImageRepository,
|
||||
image_data_in_minio: ImageData,
|
||||
):
|
||||
"""Test deleting image data."""
|
||||
# ARRANGE
|
||||
image_name = image_data_in_minio.name
|
||||
# ACT
|
||||
image_repo.remove_data(image_name)
|
||||
received_image_data = image_repo.get_data(image_name)
|
||||
# ASSERT
|
||||
assert received_image_data is None
|
||||
|
||||
|
||||
def test_should_put_image_data(
|
||||
image_repo: ImageRepository,
|
||||
image_data: ImageData,
|
||||
):
|
||||
"""Test putting image data."""
|
||||
# ARRANGE
|
||||
image_name = image_data.name
|
||||
# ACT
|
||||
image_repo.put_data(image_data)
|
||||
received_image_data = image_repo.get_data(image_name)
|
||||
# ASSERT
|
||||
assert received_image_data is not None
|
||||
assert same_image_data(image_data, received_image_data)
|
||||
|
||||
|
||||
def test_should_update_image_data(
|
||||
image_repo: ImageRepository,
|
||||
image_data_in_minio: ImageData,
|
||||
):
|
||||
"""Test updating image data."""
|
||||
# ARRANGE
|
||||
updated_image_data = image_data_in_minio.model_copy(
|
||||
update={
|
||||
'image': Image.new(mode='RGB', size=(480, 480), color='white'),
|
||||
},
|
||||
)
|
||||
image_name = updated_image_data.name
|
||||
# ACT
|
||||
image_repo.put_data(updated_image_data)
|
||||
received_image_data = image_repo.get_data(image_name)
|
||||
# ASSERT
|
||||
assert not same_image_data(image_data_in_minio, updated_image_data)
|
||||
assert received_image_data is not None
|
||||
assert same_image_data(updated_image_data, received_image_data)
|
||||
|
||||
|
||||
def test_should_list_names(
|
||||
image_repo: ImageRepository,
|
||||
image_data_in_minio: ImageData,
|
||||
):
|
||||
"""Test get all image names."""
|
||||
# ARRANGE
|
||||
updated_image_data = image_data_in_minio.model_copy(
|
||||
update={
|
||||
'name': 'updated-test-image',
|
||||
},
|
||||
)
|
||||
image_repo.put_data(updated_image_data)
|
||||
expected_name_list = [
|
||||
image_data_in_minio.name,
|
||||
updated_image_data.name,
|
||||
]
|
||||
# ACT
|
||||
name_list = image_repo.list_names()
|
||||
# ASSERT
|
||||
assert len(name_list) == 2
|
||||
for name in name_list:
|
||||
assert name in expected_name_list
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-s', '-v', __file__])
|
||||
@@ -0,0 +1,133 @@
|
||||
"""Integration tests related to Minio implementation."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.repositories.src.implementations import MinioImplementation
|
||||
|
||||
|
||||
def same_data(
|
||||
data_a: BytesIO,
|
||||
data_b: BytesIO,
|
||||
) -> bool:
|
||||
"""Check if two BytesIO-objects contain the same data."""
|
||||
assert isinstance(data_a, BytesIO)
|
||||
assert isinstance(data_b, BytesIO)
|
||||
# prepare for being read
|
||||
data_a.seek(0)
|
||||
data_b.seek(0)
|
||||
# convert to bytes
|
||||
data_a_bytes = data_a.read()
|
||||
data_b_bytes = data_b.read()
|
||||
# compare size
|
||||
if len(data_a_bytes) != len(data_b_bytes):
|
||||
logging.error(
|
||||
'data has different length: %s and %s',
|
||||
len(data_a_bytes),
|
||||
len(data_b_bytes),
|
||||
)
|
||||
return False
|
||||
# compare content
|
||||
if data_a_bytes != data_b_bytes:
|
||||
logging.error('data has different bytes')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def test_should_connect_to_minio():
|
||||
"""Test connection to Minio."""
|
||||
# ARRANGE
|
||||
client = MinioImplementation()
|
||||
# ACT
|
||||
client.connect()
|
||||
# ASSERT
|
||||
assert client.connected()
|
||||
client.close()
|
||||
|
||||
|
||||
def test_should_have_context_handler():
|
||||
"""Test that class has a working context handler implemented."""
|
||||
# ACT
|
||||
with MinioImplementation() as client:
|
||||
# ASSERT
|
||||
assert client.connected()
|
||||
|
||||
|
||||
def test_should_get_data(
|
||||
minio_client: MinioImplementation,
|
||||
buffer_in_minio: tuple[Path, BytesIO],
|
||||
):
|
||||
"""Test getting data from Minio."""
|
||||
# ARRANGE
|
||||
path, buffer = buffer_in_minio
|
||||
# ACT
|
||||
received_buffer = minio_client._get(path)
|
||||
# ASSERT
|
||||
assert received_buffer is not None
|
||||
assert same_data(received_buffer, buffer)
|
||||
|
||||
|
||||
def test_should_get_none_when_no_data(
|
||||
minio_client: MinioImplementation,
|
||||
):
|
||||
"""Test getting None when no data is available in Minio."""
|
||||
# ARRANGE
|
||||
nonexistent_path = Path('nonexistent-object-name')
|
||||
# ACT
|
||||
received_buffer = minio_client._get(nonexistent_path)
|
||||
# ASSERT
|
||||
assert received_buffer is None
|
||||
|
||||
|
||||
def test_should_delete_data(
|
||||
minio_client: MinioImplementation,
|
||||
buffer_in_minio: tuple[Path, BytesIO],
|
||||
):
|
||||
"""Test deleting data from Minio."""
|
||||
# ARRANGE
|
||||
path, _ = buffer_in_minio
|
||||
# ACT
|
||||
minio_client._delete(path)
|
||||
# ASSERT
|
||||
received_buffer = minio_client._get(path)
|
||||
assert received_buffer is None
|
||||
|
||||
|
||||
def test_should_put_data(
|
||||
minio_client: MinioImplementation,
|
||||
buffer: BytesIO,
|
||||
):
|
||||
"""Test putting data in Minio."""
|
||||
# ARRANGE
|
||||
path = Path(os.getenv('MINIO_OBJECT_NAME', default=''))
|
||||
# ACT
|
||||
minio_client._put(path, buffer)
|
||||
received_buffer = minio_client._get(path)
|
||||
# ASSERT
|
||||
assert received_buffer is not None
|
||||
assert same_data(received_buffer, buffer)
|
||||
|
||||
|
||||
def test_should_update_data(
|
||||
minio_client: MinioImplementation,
|
||||
buffer_in_minio: tuple[Path, BytesIO],
|
||||
):
|
||||
"""Test updating data in Minio."""
|
||||
# ARRANGE
|
||||
path, buffer = buffer_in_minio
|
||||
updated_buffer = BytesIO(buffer.getvalue() + b'extra data')
|
||||
# ACT
|
||||
minio_client._put(path, updated_buffer)
|
||||
received_buffer = minio_client._get(path)
|
||||
# ASSERT
|
||||
assert not same_data(updated_buffer, buffer)
|
||||
assert received_buffer is not None
|
||||
assert same_data(received_buffer, updated_buffer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-s', '-v', __file__])
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Integration tests for ModelRepository class."""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.repositories import ModelData, ModelRepository
|
||||
|
||||
|
||||
def same_buffer(
|
||||
buffer_a: BytesIO,
|
||||
buffer_b: BytesIO,
|
||||
) -> bool:
|
||||
"""Check if 2 buffers contain the same data."""
|
||||
assert isinstance(buffer_a, BytesIO)
|
||||
assert isinstance(buffer_b, BytesIO)
|
||||
# read buffers
|
||||
a_values = buffer_a.getvalue()
|
||||
b_values = buffer_b.getvalue()
|
||||
# compare length of buffers
|
||||
if len(a_values) != len(b_values):
|
||||
return False
|
||||
# compare content of buffers
|
||||
if a_values != b_values:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def same_model_data(
|
||||
data_a: ModelData,
|
||||
data_b: ModelData,
|
||||
) -> bool:
|
||||
"""Check if to ModelData-objects contain the same data."""
|
||||
assert isinstance(data_a, ModelData)
|
||||
assert isinstance(data_b, ModelData)
|
||||
# compare names
|
||||
if data_a.buffer_checksum != data_b.buffer_checksum:
|
||||
return False
|
||||
# compare buffer
|
||||
if not same_buffer(data_a.buffer, data_b.buffer):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def test_should_have_context_handler():
|
||||
"""Test that class has a working context handler implemented."""
|
||||
# ACT
|
||||
with ModelRepository() as repo:
|
||||
# ASSERT
|
||||
assert repo.connected()
|
||||
|
||||
|
||||
def test_should_get_model_data(
|
||||
model_repo: ModelRepository,
|
||||
model_data_in_minio: ModelData,
|
||||
):
|
||||
"""Test getting model data."""
|
||||
# ARRANGE
|
||||
object_name = ModelRepository._build_object_name(model_data_in_minio)
|
||||
# ACT
|
||||
received_model_data = model_repo.get_data(object_name)
|
||||
# ASSERT
|
||||
assert received_model_data is not None
|
||||
assert isinstance(received_model_data, ModelData)
|
||||
assert same_model_data(model_data_in_minio, received_model_data)
|
||||
|
||||
|
||||
def test_should_get_none_when_no_model_data(
|
||||
model_repo: ModelRepository,
|
||||
model_data: ModelData,
|
||||
):
|
||||
"""Test getting None whne no data is available."""
|
||||
# ARRANGE
|
||||
object_name = ModelRepository._build_object_name(model_data)
|
||||
# ACT
|
||||
received_model_data = model_repo.get_data(object_name)
|
||||
# ASSERT
|
||||
assert received_model_data is None
|
||||
|
||||
|
||||
def test_should_delete_model_data(
|
||||
model_repo: ModelRepository,
|
||||
model_data_in_minio: ModelData,
|
||||
):
|
||||
"""Test deleting model data."""
|
||||
# ARRANGE
|
||||
object_name = ModelRepository._build_object_name(model_data_in_minio)
|
||||
# ACT
|
||||
model_repo.remove_data(object_name)
|
||||
received_model_data = model_repo.get_data(object_name)
|
||||
# ASSERT
|
||||
assert received_model_data is None
|
||||
|
||||
|
||||
def test_should_put_model_data(
|
||||
model_repo: ModelRepository,
|
||||
model_data: ModelData,
|
||||
):
|
||||
"""Test putting model data."""
|
||||
# ARRANGE
|
||||
object_name = ModelRepository._build_object_name(model_data)
|
||||
# ACT
|
||||
model_repo.put_data(model_data)
|
||||
received_model_data = model_repo.get_data(object_name)
|
||||
# ASSERT
|
||||
assert received_model_data is not None
|
||||
assert same_model_data(model_data, received_model_data)
|
||||
|
||||
|
||||
def test_should_list_names(
|
||||
model_repo: ModelRepository,
|
||||
model_data_in_minio: ModelData,
|
||||
):
|
||||
"""Test get all model names."""
|
||||
# ARRANGE
|
||||
new_buffer_checksum = ModelData.calculate_checksum(model_data_in_minio.buffer)
|
||||
updated_model_data = model_data_in_minio.model_copy(
|
||||
update={
|
||||
'buffer_checksum': new_buffer_checksum,
|
||||
},
|
||||
)
|
||||
model_repo.put_data(updated_model_data)
|
||||
expected_name_list = [
|
||||
ModelRepository._build_object_name(model_data_in_minio),
|
||||
ModelRepository._build_object_name(updated_model_data),
|
||||
]
|
||||
# ACT
|
||||
name_list = model_repo.list_names()
|
||||
# ASSERT
|
||||
assert len(name_list) == 2
|
||||
for name in name_list:
|
||||
assert name in expected_name_list
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-s', '-v', __file__])
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Integration tests related to Mongo implementation."""
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.repositories.src.implementations import MongoImplementation
|
||||
|
||||
|
||||
def test_should_connect_mongo():
|
||||
"""Test connecting to Mongo."""
|
||||
# ARRANGE
|
||||
client = MongoImplementation()
|
||||
# ACT
|
||||
client.connect()
|
||||
# ASSERT
|
||||
assert client.connected()
|
||||
client.close()
|
||||
|
||||
|
||||
def test_should_have_context_handler():
|
||||
"""Test that class has a working context handler implemented."""
|
||||
# ACT
|
||||
with MongoImplementation() as client:
|
||||
# ASSERT
|
||||
assert client.connected()
|
||||
|
||||
|
||||
def test_should_get_data(
|
||||
mongo_client: MongoImplementation,
|
||||
dictionary_in_mongo: dict,
|
||||
):
|
||||
"""Test getting data from mongo."""
|
||||
# ARRANGE
|
||||
name = dictionary_in_mongo['name']
|
||||
query = {'name': name}
|
||||
# ACT
|
||||
received_dictionary = mongo_client._get(query)
|
||||
# ASSERT
|
||||
assert received_dictionary is not None
|
||||
assert isinstance(received_dictionary, dict)
|
||||
assert received_dictionary == dictionary_in_mongo
|
||||
|
||||
|
||||
def test_should_get_none_when_no_data(
|
||||
mongo_client: MongoImplementation,
|
||||
dictionary: dict,
|
||||
):
|
||||
"""Test getting None when no data is available in mongo."""
|
||||
# ARRANGE
|
||||
name = dictionary['name']
|
||||
query = {'name': name}
|
||||
# ACT
|
||||
received_dictionary = mongo_client._get(query)
|
||||
# ASSERT
|
||||
assert received_dictionary is None
|
||||
|
||||
|
||||
def test_should_delete_data(
|
||||
mongo_client: MongoImplementation,
|
||||
dictionary_in_mongo: dict,
|
||||
):
|
||||
"""Test deleting data from mongo."""
|
||||
# ARRANGE
|
||||
name = dictionary_in_mongo['name']
|
||||
query = {'name': name}
|
||||
# ACT
|
||||
mongo_client._delete(query)
|
||||
received_dictionary = mongo_client._get(query)
|
||||
# ASSERT
|
||||
assert received_dictionary is None
|
||||
|
||||
|
||||
def test_should_save_data(
|
||||
mongo_client: MongoImplementation,
|
||||
dictionary: dict,
|
||||
):
|
||||
"""Test saving data to mongo."""
|
||||
# ARRANGE
|
||||
name = dictionary['name']
|
||||
query = {'name': name}
|
||||
# ACT
|
||||
mongo_client._save(
|
||||
data=dictionary,
|
||||
query=query,
|
||||
)
|
||||
# ASSERT
|
||||
received_dictionary = mongo_client._get(query)
|
||||
assert received_dictionary is not None
|
||||
assert isinstance(received_dictionary, dict)
|
||||
assert received_dictionary == dictionary
|
||||
|
||||
|
||||
def test_should_update_data(
|
||||
mongo_client: MongoImplementation,
|
||||
dictionary_in_mongo: dict,
|
||||
):
|
||||
"""Test updating data in mongo."""
|
||||
# ARRANGE
|
||||
name = dictionary_in_mongo['name']
|
||||
query = {'name': name}
|
||||
# ACT
|
||||
dictionary_in_mongo['str-key'] = 'updated-value'
|
||||
mongo_client._save(
|
||||
data=dictionary_in_mongo,
|
||||
query=query,
|
||||
)
|
||||
# ASSERT
|
||||
received_dictionary = mongo_client._get(query)
|
||||
assert received_dictionary is not None
|
||||
assert received_dictionary == dictionary_in_mongo
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-s', '-v', __file__])
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Integration tests for VisualCommunicationRepository class."""
|
||||
|
||||
import pytest
|
||||
|
||||
from shared.repositories import (
|
||||
VisualCommunicationData,
|
||||
VisualCommunicationRepository,
|
||||
VisualCommunicationValues,
|
||||
)
|
||||
|
||||
|
||||
def test_should_have_context_handler():
|
||||
"""Test that class has a working context handler implemented."""
|
||||
# ACT
|
||||
with VisualCommunicationRepository() as repo:
|
||||
# ASSERT
|
||||
assert repo.connected()
|
||||
|
||||
|
||||
def test_should_get_visual_communication_data(
|
||||
visual_communication_repo: VisualCommunicationRepository,
|
||||
visual_communication_data_in_mongo: VisualCommunicationData,
|
||||
):
|
||||
"""Test getting visual communication data."""
|
||||
# ARRANGE
|
||||
name = visual_communication_data_in_mongo.name
|
||||
# ACT
|
||||
received_visual_communication_data = visual_communication_repo.get_data(name)
|
||||
# ASSERT
|
||||
assert received_visual_communication_data is not None
|
||||
assert isinstance(received_visual_communication_data, VisualCommunicationData)
|
||||
assert received_visual_communication_data == visual_communication_data_in_mongo
|
||||
|
||||
|
||||
def test_should_get_none_when_no_visual_communication_data(
|
||||
visual_communication_repo: VisualCommunicationRepository,
|
||||
visual_communication_data: VisualCommunicationData,
|
||||
):
|
||||
"""Test getting None when no data is available."""
|
||||
# ARRANGE
|
||||
name = visual_communication_data.name
|
||||
# ACT
|
||||
received_visual_communication_data = visual_communication_repo.get_data(name)
|
||||
# ASSERT
|
||||
assert received_visual_communication_data is None
|
||||
|
||||
|
||||
def test_should_delete_image_data(
|
||||
visual_communication_repo: VisualCommunicationRepository,
|
||||
visual_communication_data_in_mongo: VisualCommunicationData,
|
||||
):
|
||||
"""Test deleting visual communication data."""
|
||||
# ARRANGE
|
||||
name = visual_communication_data_in_mongo.name
|
||||
# ACT
|
||||
visual_communication_repo.remove_data(name)
|
||||
received_visual_communication_data = visual_communication_repo.get_data(name)
|
||||
# ASSERT
|
||||
assert received_visual_communication_data is None
|
||||
|
||||
|
||||
def test_should_put_visual_communication_data(
|
||||
visual_communication_repo: VisualCommunicationRepository,
|
||||
visual_communication_data: VisualCommunicationData,
|
||||
):
|
||||
"""Test putting visual communication data."""
|
||||
# ARRANGE
|
||||
name = visual_communication_data.name
|
||||
# ACT
|
||||
visual_communication_repo.put_data(visual_communication_data)
|
||||
received_visual_communication_data = visual_communication_repo.get_data(name)
|
||||
# ASSERT
|
||||
assert received_visual_communication_data is not None
|
||||
assert isinstance(received_visual_communication_data, VisualCommunicationData)
|
||||
assert received_visual_communication_data == visual_communication_data
|
||||
|
||||
|
||||
def test_should_update_visual_communication_data(
|
||||
visual_communication_repo: VisualCommunicationRepository,
|
||||
visual_communication_data_in_mongo: VisualCommunicationData,
|
||||
):
|
||||
"""Test updating visual communication data."""
|
||||
# ARRANGE
|
||||
name = visual_communication_data_in_mongo.name
|
||||
updated_annotation = VisualCommunicationValues.from_random()
|
||||
updated_visual_communication = VisualCommunicationData(
|
||||
name=name,
|
||||
annotation=updated_annotation,
|
||||
)
|
||||
# ACT
|
||||
visual_communication_repo.put_data(updated_visual_communication)
|
||||
received_visual_communication_data = visual_communication_repo.get_data(name)
|
||||
# ASSERT
|
||||
assert received_visual_communication_data is not None
|
||||
assert isinstance(received_visual_communication_data, VisualCommunicationData)
|
||||
assert received_visual_communication_data == updated_visual_communication
|
||||
|
||||
|
||||
def test_should_list_names(
|
||||
visual_communication_repo: VisualCommunicationRepository,
|
||||
visual_communication_data_in_mongo: VisualCommunicationData,
|
||||
):
|
||||
"""Test listing names of all documents."""
|
||||
# ARRANGE
|
||||
name = 'test-visual-communication-2'
|
||||
annotation = VisualCommunicationValues.from_random()
|
||||
second_visual_communication = VisualCommunicationData(
|
||||
name=name,
|
||||
annotation=annotation,
|
||||
)
|
||||
visual_communication_repo.put_data(second_visual_communication)
|
||||
expected_name_list = [
|
||||
visual_communication_data_in_mongo.name,
|
||||
second_visual_communication.name,
|
||||
]
|
||||
# ACT
|
||||
name_list = visual_communication_repo.list_names()
|
||||
# ASSERT
|
||||
assert len(name_list) == 2
|
||||
for name in name_list:
|
||||
assert name in expected_name_list
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-s', '-v', __file__])
|
||||
Reference in New Issue
Block a user