diff --git a/app/ro_crates/routes/get_routes.py b/app/ro_crates/routes/get_routes.py index 4298a79..d6b23c6 100644 --- a/app/ro_crates/routes/get_routes.py +++ b/app/ro_crates/routes/get_routes.py @@ -5,7 +5,8 @@ # Copyright (c) 2025 eScience Lab, The University of Manchester from apiflask import APIBlueprint, Schema -from apiflask.fields import String +from apiflask.fields import String, Boolean +from marshmallow.fields import Nested from flask import Response from app.services.validation_service import get_ro_crate_validation_task @@ -13,8 +14,16 @@ get_routes_bp = APIBlueprint("get_routes", __name__) +class MinioConfig(Schema): + endpoint = String(required=True) + accesskey = String(required=True) + secret = String(required=True) + ssl = Boolean(required=True) + bucket = String(required=True) + + class ValidateResult(Schema): - minio_bucket = String(required=True) + minio_config = Nested(MinioConfig, required=True) root_path = String(required=False) @@ -28,7 +37,12 @@ def get_ro_crate_validation_by_id(json_data, crate_id) -> tuple[Response, int]: - **crate_id**: The RO-Crate ID. _Required_. Request Body Parameters: - - **minio_bucket**: The MinIO bucket containing the RO-Crate. _Required_ + - **minio_config**: The MinIO bucket containing the RO-Crate. _Required_ + - **endpoint**: Endpoint, e.g. 'localhost:9000' + - **accesskey**: Access key / username + - **secret**: Secret / password + - **ssl**: Use SSL encryption? True/False + - **bucket**: The MinIO bucket to access - **root_path**: The root path containing the RO-Crate. _Optional_ Returns: @@ -38,11 +52,11 @@ def get_ro_crate_validation_by_id(json_data, crate_id) -> tuple[Response, int]: - KeyError: If required parameters (`crate_id`) are missing. """ - minio_bucket = json_data["minio_bucket"] + minio_config = json_data["minio_config"] if "root_path" in json_data: root_path = json_data["root_path"] else: root_path = None - return get_ro_crate_validation_task(minio_bucket, crate_id, root_path) + return get_ro_crate_validation_task(minio_config, crate_id, root_path) diff --git a/app/ro_crates/routes/post_routes.py b/app/ro_crates/routes/post_routes.py index 075eb62..c1ebcdb 100644 --- a/app/ro_crates/routes/post_routes.py +++ b/app/ro_crates/routes/post_routes.py @@ -5,7 +5,8 @@ # Copyright (c) 2025 eScience Lab, The University of Manchester from apiflask import APIBlueprint, Schema -from apiflask.fields import String +from apiflask.fields import String, Boolean +from marshmallow.fields import Nested from flask import Response from app.services.validation_service import ( @@ -16,8 +17,16 @@ post_routes_bp = APIBlueprint("post_routes", __name__) +class MinioConfig(Schema): + endpoint = String(required=True) + accesskey = String(required=True) + secret = String(required=True) + ssl = Boolean(required=True) + bucket = String(required=True) + + class ValidateCrate(Schema): - minio_bucket = String(required=True) + minio_config = Nested(MinioConfig, required=True) root_path = String(required=False) profile_name = String(required=False) webhook_url = String(required=False) @@ -38,7 +47,12 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]: - **crate_id**: The RO-Crate ID. _Required_. Request Body Parameters: - - **minio_bucket**: The MinIO bucket containing the RO-Crate. _Required_ + - **minio_config**: The MinIO bucket containing the RO-Crate. _Required_ + - **endpoint**: Endpoint, e.g. 'localhost:9000' + - **accesskey**: Access key / username + - **secret**: Secret / password + - **ssl**: Use SSL encryption? True/False + - **bucket**: The MinIO bucket to access - **root_path**: The root path containing the RO-Crate. _Optional_ - **profile_name**: The profile name for validation. _Optional_. - **webhook_url**: The webhook URL where validation results will be sent. _Optional_. @@ -50,7 +64,7 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]: - KeyError: If required parameters (`crate_id` or `webhook_url`) are missing. """ - minio_bucket = json_data["minio_bucket"] + minio_config = json_data["minio_config"] if "root_path" in json_data: root_path = json_data["root_path"] @@ -67,7 +81,7 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]: else: profile_name = None - return queue_ro_crate_validation_task(minio_bucket, crate_id, root_path, profile_name, webhook_url) + return queue_ro_crate_validation_task(minio_config, crate_id, root_path, profile_name, webhook_url) @post_routes_bp.post("/validate_metadata") diff --git a/app/services/validation_service.py b/app/services/validation_service.py index b60ae62..67dde94 100644 --- a/app/services/validation_service.py +++ b/app/services/validation_service.py @@ -18,18 +18,19 @@ ) from app.utils.config import InvalidAPIUsage +from app.utils.minio_utils import get_minio_client logger = logging.getLogger(__name__) def queue_ro_crate_validation_task( - minio_bucket, crate_id, root_path=None, profile_name=None, webhook_url=None + minio_config, crate_id, root_path=None, profile_name=None, webhook_url=None ) -> tuple[Response, int]: """ Queues an RO-Crate for validation with Celery. - :param minio_bucket: The MinIO bucket containing the RO-Crate. + :param minio_config: Access settings for Minio instance containing the RO-Crate. :param crate_id: The ID of the RO-Crate to validate. :param root_path: The root path containing the RO-Crate. :param profile_name: The profile to validate against. @@ -39,16 +40,18 @@ def queue_ro_crate_validation_task( """ logging.info(f"Processing: {crate_id}, {profile_name}, {webhook_url}") - logging.info(f"Minio Bucket: {minio_bucket}; Root path: {root_path}") + logging.info(f"Minio Bucket: {minio_config['bucket']}; Root path: {root_path}") - if check_ro_crate_exists(minio_bucket, crate_id, root_path): + minio_client = get_minio_client(minio_config) + + if check_ro_crate_exists(minio_client, minio_config["bucket"], crate_id, root_path): logging.info("RO-Crate exists") else: logging.info("RO-Crate does not exist") raise InvalidAPIUsage(f"No RO-Crate with prefix: {crate_id}", 400) try: - process_validation_task_by_id.delay(minio_bucket, crate_id, root_path, profile_name, webhook_url) + process_validation_task_by_id.delay(minio_config, crate_id, root_path, profile_name, webhook_url) return jsonify({"message": "Validation in progress"}), 202 except Exception as e: @@ -97,14 +100,14 @@ def queue_ro_crate_metadata_validation_task( def get_ro_crate_validation_task( - minio_bucket: str, + minio_config: dict, crate_id: str, root_path: str, ) -> tuple[Response, int]: """ Retrieves an RO-Crate validation result. - :param minio_bucket: The MinIO bucket containing the RO-Crate. + :param minio_config: Access settings for Minio instance containing the RO-Crate. :param crate_id: The ID of the RO-Crate to validate. :param root_path: The root path containing the RO-Crate. :return: A tuple containing a JSON response and an HTTP status code. @@ -112,16 +115,18 @@ def get_ro_crate_validation_task( """ logging.info(f"Retrieving validation for: {crate_id}") - if check_ro_crate_exists(minio_bucket, crate_id, root_path): + minio_client = get_minio_client(minio_config) + + if check_ro_crate_exists(minio_client, minio_config["bucket"], crate_id, root_path): logging.info("RO-Crate exists") else: logging.info("RO-Crate does not exist") raise InvalidAPIUsage(f"No RO-Crate with prefix: {crate_id}", 400) - if check_validation_exists(minio_bucket, crate_id, root_path): + if check_validation_exists(minio_client, minio_config["bucket"], crate_id, root_path): logging.info("Validation result exists") else: logging.info("Validation does not exist") raise InvalidAPIUsage(f"No validation result yet for RO-Crate: {crate_id}", 400) - return return_ro_crate_validation(minio_bucket, crate_id, root_path), 200 + return return_ro_crate_validation(minio_client, minio_config["bucket"], crate_id, root_path), 200 diff --git a/app/tasks/validation_tasks.py b/app/tasks/validation_tasks.py index a842400..0a62b55 100644 --- a/app/tasks/validation_tasks.py +++ b/app/tasks/validation_tasks.py @@ -29,12 +29,12 @@ @celery.task def process_validation_task_by_id( - minio_bucket: str, crate_id: str, root_path: str, profile_name: str | None, webhook_url: str | None + minio_config: dict, crate_id: str, root_path: str, profile_name: str | None, webhook_url: str | None ) -> None: """ Background task to process the RO-Crate validation by ID. - :param minio_bucket: The MinIO bucket containing the RO-Crate. + :param minio_config: The MinIO configuration. :param crate_id: The ID of the RO-Crate to validate. :param root_path: The root path containing the RO-Crate. :param profile_name: The name of the validation profile to use. Defaults to None. @@ -45,11 +45,13 @@ def process_validation_task_by_id( # TODO: Split try statements: (1) fetch and validate; (2) write to minio; (3) webhook + minio_client = get_minio_client(minio_config) + file_path = None try: # Fetch the RO-Crate from MinIO using the provided ID: - file_path = fetch_ro_crate_from_minio(minio_bucket, crate_id, root_path) + file_path = fetch_ro_crate_from_minio(minio_client, minio_config["bucket"], crate_id, root_path) logging.info(f"Processing validation task for {file_path}") @@ -67,7 +69,7 @@ def process_validation_task_by_id( logging.info(f"RO Crate {crate_id} is invalid.") # Update the validation status in MinIO: - update_validation_status_in_minio(minio_bucket, crate_id, root_path, validation_result.to_json()) + update_validation_status_in_minio(minio_client, minio_config["bucket"], crate_id, root_path, validation_result.to_json()) # TODO: Prepare the data to send to the webhook, and send the webhook notification. @@ -192,6 +194,7 @@ def perform_ro_crate_validation( def check_ro_crate_exists( + minio_client: object, bucket_name: str, crate_id: str, root_path: str, @@ -199,7 +202,8 @@ def check_ro_crate_exists( """ Checks for the existence of an RO-Crate using the provided Crate ID. - :param minio_bucket: The MinIO bucket containing the RO-Crate. + :param minio_client: The MinIO client + :param bucket_name: The MinIO bucket containing the RO-Crate. :param crate_id: The ID of the RO-Crate to validate. :param root_path: The root path containing the RO-Crate. :return: Boolean indicating existence @@ -207,7 +211,6 @@ def check_ro_crate_exists( logging.info(f"Checking for existence of RO-Crate {crate_id}") - minio_client = get_minio_client() if find_rocrate_object_on_minio(crate_id, minio_client, bucket_name, root_path): return True else: @@ -215,6 +218,7 @@ def check_ro_crate_exists( def check_validation_exists( + minio_client: object, bucket_name: str, crate_id: str, root_path: str, @@ -222,6 +226,7 @@ def check_validation_exists( """ Checks for the existence of a validation result using the provided Crate ID. + :param minio_client: The MinIO client :param minio_bucket: The MinIO bucket containing the RO-Crate. :param crate_id: The ID of the RO-Crate to validate. :param root_path: The root path containing the RO-Crate. @@ -230,7 +235,6 @@ def check_validation_exists( logging.info(f"Checking for existence of RO-Crate {crate_id}") - minio_client = get_minio_client() if find_validation_object_on_minio(crate_id, minio_client, bucket_name, root_path): return True else: @@ -238,6 +242,7 @@ def check_validation_exists( def return_ro_crate_validation( + minio_client: object, bucket_name: str, crate_id: str, root_path: str, @@ -245,10 +250,11 @@ def return_ro_crate_validation( """ Retrieves the validation result for an RO-Crate using the provided Crate ID. + :param minio_client: The MinIO client :param crate_id: The ID of the RO-Crate that has been validated :return: The validation result """ logging.info(f"Fetching validation result for RO-Crate {crate_id}") - return get_validation_status_from_minio(bucket_name, crate_id, root_path) + return get_validation_status_from_minio(minio_client, bucket_name, crate_id, root_path) diff --git a/app/utils/minio_utils.py b/app/utils/minio_utils.py index 925af05..1612f90 100644 --- a/app/utils/minio_utils.py +++ b/app/utils/minio_utils.py @@ -9,7 +9,6 @@ import os import tempfile -from dotenv import load_dotenv from io import BytesIO from minio import Minio, S3Error from app.utils.config import InvalidAPIUsage @@ -18,18 +17,17 @@ logger = logging.getLogger(__name__) -def fetch_ro_crate_from_minio(minio_bucket: str, crate_id: str, root_path: str) -> str: +def fetch_ro_crate_from_minio(minio_client: object, minio_bucket: str, crate_id: str, root_path: str) -> str: """ Fetches an RO-Crate from MinIO based on the crate ID. Downloads the crate as a file and returns local file path. + :param minio_client: The MinIO client :param minio_bucket: The MinIO bucket containing the RO-Crate. :param crate_id: The ID of the RO-Crate to fetch from MinIO. :param root_path: The root path containing the RO-Crate. :return: The local file path where the RO-Crate is saved. """ - minio_client = get_minio_client() - rocrate_object = find_rocrate_object_on_minio(crate_id, minio_client, minio_bucket, root_path) rocrate_minio_path = rocrate_object.object_name @@ -63,10 +61,11 @@ def fetch_ro_crate_from_minio(minio_bucket: str, crate_id: str, root_path: str) return local_root_path -def update_validation_status_in_minio(minio_bucket: str, crate_id: str, root_path: str, validation_status: str) -> None: +def update_validation_status_in_minio(minio_client: object, minio_bucket: str, crate_id: str, root_path: str, validation_status: str) -> None: """ Uploads the validation status to the MinIO bucket. + :param minio_client: The MinIO client :param minio_bucket: The MinIO bucket containing the RO-Crate. :param crate_id: The ID of the RO-Crate in MinIO :param validation_status: The validation result to upload @@ -85,9 +84,6 @@ def update_validation_status_in_minio(minio_bucket: str, crate_id: str, root_pat validation_string = json.dumps(json.loads(validation_status), indent=None).encode("utf-8") try: - - minio_client = get_minio_client() - minio_client.put_object( minio_bucket, object_name, @@ -113,11 +109,12 @@ def update_validation_status_in_minio(minio_bucket: str, crate_id: str, root_pat ) -def get_validation_status_from_minio(minio_bucket: str, crate_id: str, root_path: str) -> dict: +def get_validation_status_from_minio(minio_client: object, minio_bucket: str, crate_id: str, root_path: str) -> dict: """ Checks for the existence of a validation report for the given RO-Crate in the MinIO bucket. Returns validation message if it exists, or notification that it is missing if not. + :param minio_client: The MinIO client :param minio_bucket: The MinIO bucket containing the RO-Crate. :param crate_id: The ID of the RO-Crate in MinIO :return validation_status: Either the validation status, or note that this does not exist @@ -133,9 +130,6 @@ def get_validation_status_from_minio(minio_bucket: str, crate_id: str, root_path logging.info(f"Getting object {object_name}") try: - - minio_client = get_minio_client() - response = minio_client.get_object( minio_bucket, object_name, @@ -306,20 +300,24 @@ def get_minio_object_list(object_path: str, minio_client, minio_bucket: str, rec return object_list -def get_minio_client() -> Minio: +def get_minio_client(minio_config: dict) -> Minio: """ - Initialises the MinIO client from environment variables. + Initialises the MinIO client from provided settings. + :param minio_config: A dictionary containing the below parameters + :param endpoint: A string containing host and port. E.g. 'localhost:9000' + :param access_key: A string containing the access key / username + :param secret_key: A string containing the secret key / password + :param use_ssl: Boolean defining if SSL connection should be used or not :return: The MinIO client. :raises ValueError: If required environment variables are not set. """ - load_dotenv() minio_client = Minio( - endpoint=os.environ.get("MINIO_ENDPOINT"), - access_key=os.environ.get("MINIO_ROOT_USER"), - secret_key=os.environ.get("MINIO_ROOT_PASSWORD"), - secure=False, + endpoint=minio_config["endpoint"], + access_key=minio_config["accesskey"], + secret_key=minio_config["secret"], + secure=minio_config["ssl"], ) return minio_client diff --git a/tests/test_api_routes.py b/tests/test_api_routes.py index e44fe7e..f527501 100644 --- a/tests/test_api_routes.py +++ b/tests/test_api_routes.py @@ -17,7 +17,13 @@ def client(): [ ( "crate-123", { - "minio_bucket": "test_bucket", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, "root_path": "base_path", "webhook_url": "https://webhook.example.com", "profile_name": "default" @@ -25,28 +31,52 @@ def client(): ), ( "crate-123", { - "minio_bucket": "test_bucket", - "root_path": "base_path", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "root_path": "base_path", "webhook_url": "https://webhook.example.com", }, 202, {"message": "Validation in progress"} ), ( "crate-123", { - "minio_bucket": "test_bucket", - "root_path": "base_path", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "root_path": "base_path", "profile_name": "default" }, 202, {"message": "Validation in progress"} ), ( "crate-123", { - "minio_bucket": "test_bucket", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, "webhook_url": "https://webhook.example.com", "profile_name": "default" }, 202, {"message": "Validation in progress"} ), ( "crate-123", { - "minio_bucket": "test_bucket" + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, }, 202, {"message": "Validation in progress"} ), ], @@ -60,13 +90,13 @@ def test_validate_by_id_success(client: FlaskClient, crate_id: str, payload: dic response = client.post(f"/v1/ro_crates/{crate_id}/validation", json=payload) - minio_bucket = payload["minio_bucket"] if "minio_bucket" in payload else None + minio_config = payload["minio_config"] if "minio_config" in payload else None root_path = payload["root_path"] if "root_path" in payload else None profile_name = payload["profile_name"] if "profile_name" in payload else None webhook_url = payload["webhook_url"] if "webhook_url" in payload else None assert response.status_code == status_code assert response.json == response_json - mock_queue.assert_called_once_with(minio_bucket, crate_id, root_path, profile_name, webhook_url) + mock_queue.assert_called_once_with(minio_config, crate_id, root_path, profile_name, webhook_url) @pytest.mark.parametrize( @@ -170,13 +200,25 @@ def test_validate_metadata_failure(client: FlaskClient, payload: dict, status_co "crate_id, payload, status_code", [ ( - "",{ - "minio_bucket": "test_bucket", + "", { + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, "root_path": "base_path" }, 404 ), ( - "crate-123",{ + "crate-123", { + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + }, "root_path": "base_path" }, 422 ), @@ -191,7 +233,13 @@ def test_get_validation_by_id_failures(client: FlaskClient, crate_id: str, paylo def test_get_validation_by_id_success(client): crate_id = "crate-123" payload = { - "minio_bucket": "test_bucket", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, "root_path": "base_path" } @@ -202,20 +250,26 @@ def test_get_validation_by_id_success(client): assert response.status_code == 200 assert response.json == {"status": "valid"} - mock_get.assert_called_once_with("test_bucket", "crate-123", "base_path") + mock_get.assert_called_once_with(payload["minio_config"], "crate-123", "base_path") def test_get_validation_by_id_missing_root_path(client): crate_id = "crate-123" payload = { - "minio_bucket": "test_bucket", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + } } with patch("app.ro_crates.routes.get_routes.get_ro_crate_validation_task") as mock_get: - mock_get.return_value = ({"message": "Validation in progress"}, 202) + mock_get.return_value = ({"status": "valid"}, 200) response = client.get(f"/v1/ro_crates/{crate_id}/validation", json=payload) - assert response.status_code == 202 - assert response.json == {"message": "Validation in progress"} - mock_get.assert_called_once_with("test_bucket", "crate-123", None) + assert response.status_code == 200 + assert response.json == {"status": "valid"} + mock_get.assert_called_once_with(payload["minio_config"], "crate-123", None) diff --git a/tests/test_integration.py b/tests/test_integration.py index 4d7e5ec..2b4b9a7 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -104,7 +104,13 @@ def test_no_rocrate_for_validation(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates" + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + } } response = requests.post(url, json=payload, headers=headers) @@ -130,7 +136,13 @@ def test_no_validation_result_for_missing_crate(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates" + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + } } # GET action and tests @@ -156,7 +168,13 @@ def test_get_existing_validation_result(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates" + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + } } # GET action and tests @@ -182,7 +200,13 @@ def test_rocrate_not_validated_yet(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates" + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + } } # GET action and tests @@ -209,7 +233,13 @@ def test_zipped_rocrate_validation(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates" + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + } } # POST action and tests @@ -266,7 +296,13 @@ def test_directory_rocrate_validation(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates" + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + } } # POST action and tests @@ -322,7 +358,13 @@ def test_ignore_rocrates_not_on_basepath(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates" + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + } } # POST action and tests @@ -350,7 +392,13 @@ def test_zipped_rocrate_in_subdirectory_validation(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates", + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + }, "root_path" : subdir_path } @@ -409,7 +457,13 @@ def test_directory_rocrate_in_subdirectory_validation(): # The API expects the JSON to be passed as a string payload = { - "minio_bucket" : "ro-crates", + "minio_config": { + "endpoint": "minio:9000", + "accesskey": "minioadmin", + "secret": "minioadmin", + "ssl": False, + "bucket": "ro-crates" + }, "root_path" : subdir_path } diff --git a/tests/test_minio.py b/tests/test_minio.py index 57c8c59..426d901 100644 --- a/tests/test_minio.py +++ b/tests/test_minio.py @@ -22,14 +22,29 @@ def __init__(self, name, is_dir=False): # Testing function: get_minio_client -def test_get_minio_client_success(monkeypatch): - # Set required env vars - monkeypatch.setenv("MINIO_ENDPOINT", "localhost:9000") - monkeypatch.setenv("MINIO_ROOT_USER", "admin") - monkeypatch.setenv("MINIO_ROOT_PASSWORD", "password123") +@pytest.mark.parametrize( + "minio_config", + [ + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False + }, + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "ignore_this" + } + ], + ids=["base_case", "ignore_extra_items"] +) +def test_get_minio_client_success(minio_config: dict): from app.utils.minio_utils import get_minio_client - client = get_minio_client() + client = get_minio_client(minio_config) assert isinstance(client, Minio) assert client._base_url.host == "localhost:9000" @@ -268,10 +283,9 @@ def test_download_s3error( def test_successful_retrieval(mocker, mock_minio_response): mock_client = MagicMock() mock_client.get_object.return_value = mock_minio_response - mocker.patch("app.utils.minio_utils.get_minio_client", return_value=mock_client) from app.utils.minio_utils import get_validation_status_from_minio - result = get_validation_status_from_minio("test_bucket", "crate123", None) + result = get_validation_status_from_minio(mock_client, "test_bucket", "crate123", None) assert result == {"status": "valid"} mock_minio_response.close.assert_called_once() @@ -310,11 +324,10 @@ def test_get_validation_error_raised( ): mock_client = MagicMock() mock_client.get_object.side_effect = get_side_effect - mocker.patch("app.utils.minio_utils.get_minio_client", return_value=mock_client) from app.utils.minio_utils import get_validation_status_from_minio, InvalidAPIUsage with pytest.raises(InvalidAPIUsage) as exc: - get_validation_status_from_minio(bucket, crateid, root_path) + get_validation_status_from_minio(mock_client, bucket, crateid, root_path) assert exc.value.status_code == status_code assert error_check in str(exc.value.message) @@ -322,16 +335,14 @@ def test_get_validation_error_raised( # Testing function: update_validation_status_in_minio -@mock.patch("app.utils.minio_utils.get_minio_client") -def test_update_validation_status_success(mock_get_client): +def test_update_validation_status_success(): mock_minio_client = mock.Mock() - mock_get_client.return_value = mock_minio_client crate_id = "crate123" validation_status = json.dumps({"status": "valid", "errors": []}) from app.utils.minio_utils import update_validation_status_in_minio - update_validation_status_in_minio("test_bucket", crate_id, "", validation_status) + update_validation_status_in_minio(mock_minio_client, "test_bucket", crate_id, "", validation_status) expected_object_name = f"{crate_id}_validation/validation_status.txt" expected_data = json.dumps(json.loads(validation_status), indent=None).encode("utf-8") @@ -385,19 +396,16 @@ def test_update_validation_status_success(mock_get_client): ], ids=["s3error", "value_error", "unexpected_error"] ) -@mock.patch("app.utils.minio_utils.get_minio_client") def test_update_validation_status_erro( - mock_get_client, bucket: str, crateid: str, root_path: str, validation_result: dict, put_side_effect, error_check: str, status_code: int ): mock_minio_client = mock.Mock() - mock_get_client.return_value = mock_minio_client mock_minio_client.put_object.side_effect = put_side_effect from app.utils.minio_utils import update_validation_status_in_minio, InvalidAPIUsage with pytest.raises(InvalidAPIUsage) as exc: - update_validation_status_in_minio(bucket, crateid, root_path, json.dumps(validation_result)) + update_validation_status_in_minio(mock_minio_client, bucket, crateid, root_path, json.dumps(validation_result)) assert exc.value.status_code == status_code assert error_check in str(exc.value.message) @@ -408,16 +416,14 @@ def test_update_validation_status_erro( @patch("app.utils.minio_utils.download_file_from_minio") @patch("app.utils.minio_utils.get_minio_object_list") @patch("app.utils.minio_utils.find_rocrate_object_on_minio") -@patch("app.utils.minio_utils.get_minio_client") def test_fetch_rocrate_zip( - mock_get_client, mock_find_object, mock_get_list, mock_download, tmp_path, ): # Setup mocks - mock_get_client.return_value = "minio_client" + minio_client = "minio_client" rocrate_obj = DummyObject("some/path/rocrate123.zip", is_dir=False) mock_find_object.return_value = rocrate_obj @@ -425,7 +431,7 @@ def test_fetch_rocrate_zip( with patch("app.utils.minio_utils.tempfile.mkdtemp", return_value=str(tmp_path)): # Execute - result = fetch_ro_crate_from_minio("test_bucket", "rocrate123", "some/path") + result = fetch_ro_crate_from_minio(minio_client, "test_bucket", "rocrate123", "some/path") # Assert expected_path = tmp_path / "rocrate123.zip" @@ -438,16 +444,14 @@ def test_fetch_rocrate_zip( @patch("app.utils.minio_utils.download_file_from_minio") @patch("app.utils.minio_utils.get_minio_object_list") @patch("app.utils.minio_utils.find_rocrate_object_on_minio") -@patch("app.utils.minio_utils.get_minio_client") def test_fetch_rocrate_directory( - mock_get_client, mock_find_object, mock_get_list, mock_download, tmp_path, ): # Setup mocks - mock_get_client.return_value = "minio_client" + minio_client = "minio_client" rocrate_obj = DummyObject("rocrates/rocrate124", is_dir=True) mock_find_object.return_value = rocrate_obj @@ -461,7 +465,7 @@ def test_fetch_rocrate_directory( ] # Execute - result = fetch_ro_crate_from_minio("test_bucket", "rocrate124", "rocrates") + result = fetch_ro_crate_from_minio(minio_client, "test_bucket", "rocrate124", "rocrates") # Assert expected_root = tmp_path / "rocrate124" @@ -481,15 +485,13 @@ def test_fetch_rocrate_directory( @patch("app.utils.minio_utils.download_file_from_minio") @patch("app.utils.minio_utils.get_minio_object_list") @patch("app.utils.minio_utils.find_rocrate_object_on_minio") -@patch("app.utils.minio_utils.get_minio_client") def test_fetch_rocrate_handles_empty_dir( - mock_get_client, mock_find_object, mock_get_list, mock_download, tmp_path, ): - mock_get_client.return_value = "minio_client" + minio_client = "minio_client" rocrate_obj = DummyObject("rocrate456", is_dir=True) mock_find_object.return_value = rocrate_obj mock_get_list.return_value = [] @@ -497,7 +499,7 @@ def test_fetch_rocrate_handles_empty_dir( from app.utils.minio_utils import fetch_ro_crate_from_minio with patch("app.utils.minio_utils.tempfile.mkdtemp", return_value=str(tmp_path)): - result = fetch_ro_crate_from_minio("test_bucket", "rocrate456", "") + result = fetch_ro_crate_from_minio(minio_client, "test_bucket", "rocrate456", "") expected_root = tmp_path / "rocrate456" assert result == str(expected_root) diff --git a/tests/test_services.py b/tests/test_services.py index f5838e5..c7d50c3 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -22,21 +22,33 @@ def flask_app(): # Test function: queue_ro_crate_validation_task @pytest.mark.parametrize( - "crate_id, rocrate_exists, delay_side_effects, payload, status_code, response_dict", + "crate_id, rocrate_exists, minio_client, delay_side_effects, payload, status_code, response_dict", [ ( - "crate123", True, None, + "crate123", True, "minio_client", None, { - "minio_bucket": "test_bucket", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, "root_path": "base_path", "webhook_url": "https://webhook.example.com", "profile_name": "default" }, 202, {"message": "Validation in progress"} ), ( - "crate123", True, Exception("Celery down"), + "crate123", True, "minio_client", Exception("Celery down"), { - "minio_bucket": "test_bucket", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, "root_path": "base_path", "webhook_url": "https://webhook.example.com", "profile_name": "default" @@ -45,33 +57,47 @@ def flask_app(): ], ids=["successful_queue", "celery_server_down"] ) +@patch("app.services.validation_service.process_validation_task_by_id.delay") +@patch("app.services.validation_service.check_ro_crate_exists") +@patch("app.services.validation_service.get_minio_client") def test_queue_ro_crate_validation_task( - flask_app: FlaskClient, crate_id: str, rocrate_exists: bool, + mock_client, + mock_exists, + mock_delay, + flask_app: FlaskClient, crate_id: str, rocrate_exists: bool, minio_client: str, delay_side_effects: Exception, payload: dict, status_code: int, response_dict: dict ): - minio_bucket = payload["minio_bucket"] if "minio_bucket" in payload else None + mock_delay.side_effect = delay_side_effects + mock_exists.return_value = rocrate_exists + mock_client.return_value = minio_client + + minio_config = payload["minio_config"] if "minio_config" in payload else None root_path = payload["root_path"] if "root_path" in payload else None profile_name = payload["profile_name"] if "profile_name" in payload else None webhook_url = payload["webhook_url"] if "webhook_url" in payload else None - with patch("app.services.validation_service.process_validation_task_by_id.delay", side_effect=delay_side_effects) as mock_delay: - with patch("app.services.validation_service.check_ro_crate_exists", return_value=rocrate_exists) as mock_exists: - - response, status_code = queue_ro_crate_validation_task(minio_bucket, crate_id, root_path, profile_name, webhook_url) + response, status_code = queue_ro_crate_validation_task(minio_config, crate_id, root_path, profile_name, webhook_url) - mock_exists.assert_called_once_with(minio_bucket, crate_id, root_path) - mock_delay.assert_called_once_with(minio_bucket, crate_id, root_path, profile_name, webhook_url) - assert status_code == status_code - assert response.json == response_dict + mock_client.assert_called_once_with(minio_config) + mock_exists.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, root_path) + mock_delay.assert_called_once_with(minio_config, crate_id, root_path, profile_name, webhook_url) + assert status_code == status_code + assert response.json == response_dict @pytest.mark.parametrize( - "crate_id, rocrate_exists, payload, iau_message", + "crate_id, rocrate_exists, minio_client, payload, iau_message", [ ( - "crate12z", False, + "crate12z", False, "minio_client", { - "minio_bucket": "test_bucket", + "minio_config": { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, "root_path": "base_path", "webhook_url": "https://webhook.example.com", "profile_name": "default" @@ -80,24 +106,31 @@ def test_queue_ro_crate_validation_task( ], ids=["no_rocrate_exists"] ) +@patch("app.services.validation_service.process_validation_task_by_id.delay") +@patch("app.services.validation_service.check_ro_crate_exists") +@patch("app.services.validation_service.get_minio_client") def test_queue_ro_crate_validation_task_failure( + mock_client, + mock_exists, + mock_delay, flask_app: FlaskClient, crate_id: str, rocrate_exists: bool, - payload: dict, iau_message: str + minio_client: str, payload: dict, iau_message: str ): - minio_bucket = payload["minio_bucket"] if "minio_bucket" in payload else None + mock_exists.return_value = rocrate_exists + mock_client.return_value = minio_client + + minio_config = payload["minio_config"] if "minio_config" in payload else None root_path = payload["root_path"] if "root_path" in payload else None profile_name = payload["profile_name"] if "profile_name" in payload else None webhook_url = payload["webhook_url"] if "webhook_url" in payload else None - with patch("app.services.validation_service.process_validation_task_by_id.delay") as mock_delay: - with patch("app.services.validation_service.check_ro_crate_exists", return_value=rocrate_exists) as mock_exists: - - with pytest.raises(InvalidAPIUsage) as exc_info: - queue_ro_crate_validation_task(minio_bucket, crate_id, root_path, profile_name, webhook_url) + with pytest.raises(InvalidAPIUsage) as exc_info: + queue_ro_crate_validation_task(minio_config, crate_id, root_path, profile_name, webhook_url) - assert iau_message in str(exc_info.value.message) - mock_exists.assert_called_once_with(minio_bucket, crate_id, root_path) - mock_delay.assert_not_called() + assert iau_message in str(exc_info.value.message) + mock_client.assert_called_once_with(minio_config) + mock_exists.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, root_path) + mock_delay.assert_not_called() # Test function: queue_ro_crate_metadata_validation_task @@ -170,41 +203,84 @@ def test_queue_metadata_json_errors(flask_app, crate_json: str, status_code: int # Test function: get_ro_crate_validation_task @pytest.mark.parametrize( - "crate_id, crate_exists, validation_exists, validation_value, status_code, error_message", + "minio_config, crate_id, crate_exists, validation_exists, " + + "validation_value, status_code, error_message, minio_client", [ - ("crate123", True, True, {"status": "valid"}, 200, None), - ("crate123", False, False, None, 400, "No RO-Crate with prefix: crate123"), - ("crate123", True, False, None, 400, "No validation result yet for RO-Crate: crate123"), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", True, True, {"status": "valid"}, 200, None, + "minio_client" + ), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", False, False, None, 400, "No RO-Crate with prefix: crate123", + "minio_client" + ), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", True, False, None, 400, "No validation result yet for RO-Crate: crate123", + "minio_client" + ), ], ids=["validation_exists", "rocrate_missing", "validation_missing"] ) -def test_get_validation(flask_app, crate_id: str, crate_exists: bool, - validation_exists: bool, validation_value: dict, - status_code: int, error_message: str): - with patch("app.services.validation_service.check_ro_crate_exists", return_value=crate_exists) as mock_rocrate: - with patch("app.services.validation_service.check_validation_exists", return_value=validation_exists) as mock_validation: - with patch("app.services.validation_service.return_ro_crate_validation", return_value=validation_value) as mock_return: - - if crate_exists and validation_exists: - response, status = get_ro_crate_validation_task("test_bucket", crate_id, "base_path") - - mock_return.assert_called_once_with("test_bucket", crate_id, "base_path") - mock_rocrate.assert_called_once_with("test_bucket", crate_id, "base_path") - mock_validation.assert_called_once_with("test_bucket", crate_id, "base_path") - - assert status == status_code - assert response == validation_value - - else: - with pytest.raises(InvalidAPIUsage) as exc_info: - get_ro_crate_validation_task("test_bucket", crate_id, "base_path") - - assert exc_info.value.status_code == status_code - assert error_message in str(exc_info.value.message) - - mock_rocrate.assert_called_once_with("test_bucket", crate_id, "base_path") - if crate_exists: - mock_validation.assert_called_once_with("test_bucket", crate_id, "base_path") - else: - mock_validation.assert_not_called() - mock_return.assert_not_called() +@patch("app.services.validation_service.check_ro_crate_exists") +@patch("app.services.validation_service.check_validation_exists") +@patch("app.services.validation_service.return_ro_crate_validation") +@patch("app.services.validation_service.get_minio_client") +def test_get_validation( + mock_client, + mock_return, + mock_validation, + mock_rocrate, + flask_app, minio_config: dict, crate_id: str, crate_exists: bool, + validation_exists: bool, validation_value: dict, + status_code: int, error_message: str, minio_client: str +): + mock_client.return_value = minio_client + mock_rocrate.return_value = crate_exists + mock_validation.return_value = validation_exists + mock_return.return_value = validation_value + + if crate_exists and validation_exists: + response, status = get_ro_crate_validation_task(minio_config, crate_id, "base_path") + + mock_client.assert_called_once_with(minio_config) + mock_return.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, "base_path") + mock_rocrate.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, "base_path") + mock_validation.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, "base_path") + + assert status == status_code + assert response == validation_value + + else: + with pytest.raises(InvalidAPIUsage) as exc_info: + get_ro_crate_validation_task(minio_config, crate_id, "base_path") + + assert exc_info.value.status_code == status_code + assert error_message in str(exc_info.value.message) + + mock_rocrate.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, "base_path") + if crate_exists: + mock_validation.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, "base_path") + else: + mock_validation.assert_not_called() + mock_return.assert_not_called() diff --git a/tests/test_validation_tasks.py b/tests/test_validation_tasks.py index 2ee1a0a..afa11c2 100644 --- a/tests/test_validation_tasks.py +++ b/tests/test_validation_tasks.py @@ -16,17 +16,49 @@ # Test function: process_validation_task_by_id @pytest.mark.parametrize( - "crate_id, os_path_exists, os_path_isfile, os_path_isdir, return_value, webhook, profile, val_success, val_result", + "minio_config, crate_id, os_path_exists, os_path_isfile, os_path_isdir, " + + "return_value, webhook, profile, val_success, val_result, minio_client", [ - ("crate123", True, True, False, "/tmp/crate.zip", - "https://example.com/hook", "profileA", True, '{"status": "valid"}'), - ("crate123", True, False, True, "/tmp/crate123", - "https://example.com/hook", "profileA", True, '{"status": "valid"}'), - ("crate123", True, False, True, "/tmp/crate123", - None, "profileA", True, '{"status": "valid"}'), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", True, True, False, "/tmp/crate.zip", + "https://example.com/hook", "profileA", True, '{"status": "valid"}', + "minio_client" + ), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", True, False, True, "/tmp/crate123", + "https://example.com/hook", "profileA", True, '{"status": "valid"}', + "minio_client" + ), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", True, False, True, "/tmp/crate123", + None, "profileA", True, '{"status": "valid"}', + "minio_client" + ), ], ids=["successful_validation_zip", "successful_validation_dir", "successful_validation_nowebhook"] ) +@mock.patch("app.tasks.validation_tasks.get_minio_client") @mock.patch("app.tasks.validation_tasks.shutil.rmtree") @mock.patch("app.tasks.validation_tasks.os.remove") @mock.patch("app.tasks.validation_tasks.os.path.exists") @@ -46,24 +78,27 @@ def test_process_validation( mock_exists, mock_remove, mock_rmtree, - crate_id: str, os_path_exists: bool, os_path_isfile: bool, os_path_isdir: bool, - return_value: str, webhook: str, profile: str, val_success: bool, val_result: str + mock_client, + minio_config: dict, crate_id: str, os_path_exists: bool, os_path_isfile: bool, os_path_isdir: bool, + return_value: str, webhook: str, profile: str, val_success: bool, val_result: str, minio_client: str ): mock_exists.return_value = os_path_exists mock_isfile.return_value = os_path_isfile mock_isdir.return_value = os_path_isdir mock_fetch.return_value = return_value + mock_client.return_value = minio_client mock_validation_result = mock.Mock() mock_validation_result.has_issues.return_value = val_success mock_validation_result.to_json.return_value = val_result mock_validate.return_value = mock_validation_result - process_validation_task_by_id("test_bucket", crate_id, "", profile, webhook) + process_validation_task_by_id(minio_config, crate_id, "", profile, webhook) - mock_fetch.assert_called_once_with("test_bucket", crate_id, "") + mock_client.assert_called_once_with(minio_config) + mock_fetch.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, "") mock_validate.assert_called_once_with(return_value, profile) - mock_update.assert_called_once_with("test_bucket", crate_id, "", val_result) + mock_update.assert_called_once_with(minio_client, minio_config["bucket"], crate_id, "", val_result) if webhook is not None: mock_webhook.assert_called_once_with(webhook, val_result) else: @@ -77,19 +112,50 @@ def test_process_validation( @pytest.mark.parametrize( - "crate_id, os_path_exists, os_path_isfile, os_path_isdir, return_fetch, " - + "webhook, profile, return_validate, validate_side_effect, fetch_side_effect", + "minio_config, crate_id, os_path_exists, os_path_isfile, os_path_isdir, return_fetch, " + + "webhook, profile, return_validate, validate_side_effect, fetch_side_effect, minio_client", [ - ("crate123", True, True, False, "/tmp/crate.zip", - "https://example.com/hook", "profileA", "Validation failed", None, None), - ("crate123", True, True, False, "/tmp/crate.zip", - "https://example.com/hook", "profileA", None, Exception("Unexpected error"), None), - ("crate123", False, False, False, None, - "https://example.com/hook", "profileA", None, None, Exception("MinIO fetch failed")), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", True, True, False, "/tmp/crate.zip", + "https://example.com/hook", "profileA", "Validation failed", None, None, + "minio_client" + ), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", True, True, False, "/tmp/crate.zip", + "https://example.com/hook", "profileA", None, Exception("Unexpected error"), None, + "minio_client" + ), + ( + { + "endpoint": "localhost:9000", + "accesskey": "admin", + "secret": "password123", + "ssl": False, + "bucket": "test_bucket" + }, + "crate123", False, False, False, None, + "https://example.com/hook", "profileA", None, None, Exception("MinIO fetch failed"), + "minio_client" + ), ], ids=["validation_fails_with_message", "validation_fails_with_validation_exception", "validation_fails_with_fetch_exception"] ) +@mock.patch("app.tasks.validation_tasks.get_minio_client") @mock.patch("app.tasks.validation_tasks.shutil.rmtree") @mock.patch("app.tasks.validation_tasks.os.remove") @mock.patch("app.tasks.validation_tasks.os.path.exists") @@ -109,13 +175,15 @@ def test_process_validation_failure( mock_exists, mock_remove, mock_rmtree, - crate_id: str, os_path_exists: bool, os_path_isfile: bool, os_path_isdir: bool, + mock_client, + minio_config: dict, crate_id: str, os_path_exists: bool, os_path_isfile: bool, os_path_isdir: bool, return_fetch: str, webhook: str, profile: str, return_validate: str, - validate_side_effect: Exception, fetch_side_effect: Exception + validate_side_effect: Exception, fetch_side_effect: Exception, minio_client: str ): mock_exists.return_value = os_path_exists mock_isfile.return_value = os_path_isfile mock_isdir.return_value = os_path_isdir + mock_client.return_value = minio_client if fetch_side_effect is None: mock_fetch.return_value = return_fetch @@ -127,7 +195,7 @@ def test_process_validation_failure( else: mock_validate.side_effect = validate_side_effect - process_validation_task_by_id("test_bucket", crate_id, "", profile, webhook) + process_validation_task_by_id(minio_config, crate_id, "", profile, webhook) if fetch_side_effect is None: mock_validate.assert_called_once_with(return_fetch, profile) @@ -317,10 +385,10 @@ def test_return_validation_returns_dict(mock_get_status): # Simulate dict result mock_get_status.return_value = {"status": "passed", "errors": []} - result = return_ro_crate_validation("test_bucket", "crate123", None) + result = return_ro_crate_validation("minio_client", "test_bucket", "crate123", None) assert isinstance(result, dict) assert result["status"] == "passed" - mock_get_status.assert_called_once_with("test_bucket", "crate123", None) + mock_get_status.assert_called_once_with("minio_client", "test_bucket", "crate123", None) @mock.patch("app.tasks.validation_tasks.get_validation_status_from_minio") @@ -328,10 +396,10 @@ def test_return_validation_returns_string(mock_get_status): # Simulate string result mock_get_status.return_value = "Validation result: OK" - result = return_ro_crate_validation("test_bucket", "crate456", None) + result = return_ro_crate_validation("minio_client", "test_bucket", "crate456", None) assert isinstance(result, str) assert "OK" in result - mock_get_status.assert_called_once_with("test_bucket", "crate456", None) + mock_get_status.assert_called_once_with("minio_client", "test_bucket", "crate456", None) @mock.patch("app.tasks.validation_tasks.get_validation_status_from_minio") @@ -340,63 +408,55 @@ def test_return_validation_raises_error(mock_get_status): mock_get_status.side_effect = InvalidAPIUsage("MinIO S3 Error: empty", 500) with pytest.raises(InvalidAPIUsage) as exc_info: - return_ro_crate_validation("test_bucket", "crate789", None) + return_ro_crate_validation("minio_client", "test_bucket", "crate789", None) assert "MinIO S3 Error" in str(exc_info.value.message) - mock_get_status.assert_called_once_with("test_bucket", "crate789", None) + mock_get_status.assert_called_once_with("minio_client", "test_bucket", "crate789", None) # Test function: check_ro_crate_exists @pytest.mark.parametrize( - "bucket, crate_id, base_path, client_return, ro_object_return, rocrate_exists", + "minio_client, bucket, crate_id, base_path, ro_object_return, rocrate_exists", [ - ("test_bucket", "crate123", "base_path", "mock_client", "crate123", True), - ("test_bucket", "crate12z", "base_path", "mock_client", False, False) + ("minio_client", "test_bucket", "crate123", "base_path", "crate123", True), + ("minio_client", "test_bucket", "crate12z", "base_path", False, False) ], ids=["rocrate_exists", "rocrate_does_not_exist"] ) -@mock.patch("app.tasks.validation_tasks.get_minio_client") @mock.patch("app.tasks.validation_tasks.find_rocrate_object_on_minio") def test_ro_crate_exists( mock_find_rocrate, - mock_get_client, - bucket: str, crate_id: str, base_path: str, client_return: str, + minio_client: str, bucket: str, crate_id: str, base_path: str, ro_object_return: str, rocrate_exists: bool ): - mock_get_client.return_value = client_return mock_find_rocrate.return_value = ro_object_return - result = check_ro_crate_exists(bucket, crate_id, base_path) + result = check_ro_crate_exists(minio_client, bucket, crate_id, base_path) - mock_get_client.assert_called_once() - mock_find_rocrate.assert_called_once_with(crate_id, client_return, bucket, base_path) + mock_find_rocrate.assert_called_once_with(crate_id, minio_client, bucket, base_path) assert result is rocrate_exists # Test function: check_validation_exists @pytest.mark.parametrize( - "bucket, crate_id, base_path, client_return, val_object_return, validate_exists", + "minio_client, bucket, crate_id, base_path, val_object_return, validate_exists", [ - ("test_bucket", "crate123", "base_path", "mock_client", "crate123", True), - ("test_bucket", "crate12z", "base_path", "mock_client", False, False) + ("minio_client", "test_bucket", "crate123", "base_path", "crate123", True), + ("minio_client", "test_bucket", "crate12z", "base_path", False, False) ], ids=["validation_exists", "validation_does_not_exist"] ) -@mock.patch("app.tasks.validation_tasks.get_minio_client") @mock.patch("app.tasks.validation_tasks.find_validation_object_on_minio") def test_validation_exists( mock_find_validation, - mock_get_client, - bucket: str, crate_id: str, base_path: str, client_return: str, + minio_client: str, bucket: str, crate_id: str, base_path: str, val_object_return: str, validate_exists: bool ): - mock_get_client.return_value = client_return mock_find_validation.return_value = val_object_return - result = check_validation_exists(bucket, crate_id, base_path) + result = check_validation_exists(minio_client, bucket, crate_id, base_path) - mock_get_client.assert_called_once() - mock_find_validation.assert_called_once_with(crate_id, client_return, bucket, base_path) + mock_find_validation.assert_called_once_with(crate_id, minio_client, bucket, base_path) assert result is validate_exists