From 211e60a4786c0992c35d982ad864753e38a22319 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Mon, 10 Aug 2026 20:07:05 -0600 Subject: [PATCH 1/5] remove minio code --- legal-api/pyproject.toml | 1 - legal-api/src/legal_api/config.py | 12 --- legal-api/src/legal_api/reports/report.py | 11 +-- .../business_filings/business_documents.py | 20 ++-- .../business_filings/business_filings.py | 15 ++- .../src/legal_api/resources/v2/document.py | 40 -------- legal-api/src/legal_api/services/__init__.py | 2 - .../filings/validations/common_validations.py | 7 +- legal-api/src/legal_api/services/minio.py | 94 ------------------- 9 files changed, 17 insertions(+), 185 deletions(-) delete mode 100644 legal-api/src/legal_api/services/minio.py diff --git a/legal-api/pyproject.toml b/legal-api/pyproject.toml index 969d0c388e..2b95b20c4a 100644 --- a/legal-api/pyproject.toml +++ b/legal-api/pyproject.toml @@ -25,7 +25,6 @@ dependencies = [ # FUTURE: look at removing "strict-rfc3339 (==0.7)", # FUTURE: look at removing restriction - "minio (==7.0.2)", "pypdf (>=6.12.1)", "reportlab (>=4.5.0)", # FUTURE: look at removing restriction diff --git a/legal-api/src/legal_api/config.py b/legal-api/src/legal_api/config.py index de9dbe7eae..28c51a6e53 100644 --- a/legal-api/src/legal_api/config.py +++ b/legal-api/src/legal_api/config.py @@ -137,12 +137,6 @@ class _Config: # pylint: disable=too-few-public-methods # legislative timezone for future effective dating LEGISLATIVE_TIMEZONE = os.getenv("LEGISLATIVE_TIMEZONE", "America/Vancouver") - # Minio configuration values - MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT") - MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY") - MINIO_ACCESS_SECRET = os.getenv("MINIO_ACCESS_SECRET") - MINIO_BUCKET_BUSINESSES = os.getenv("MINIO_BUCKET_BUSINESSES", "businesses") - MINIO_SECURE = True # determines which year of NAICS data will be used to drive NAICS search NAICS_YEAR = int(os.getenv("NAICS_YEAR", "2022")) @@ -303,12 +297,6 @@ class TestConfig(_Config): # pylint: disable=too-few-public-methods 4H8UZcVFN95vEKxJiLRjAmj6g273pu9kK4ymXNEjWWJn -----END RSA PRIVATE KEY-----""" - # Minio variables - MINIO_ENDPOINT = "http://dummy-minio-url" - MINIO_ACCESS_KEY = "minio" - MINIO_ACCESS_SECRET = "minio123" - MINIO_BUCKET_BUSINESSES = "businesses" - MINIO_SECURE = False # determines which year of NAICS data will be used to drive NAICS search; # matches the test seed data loaded by business_model_migrations diff --git a/legal-api/src/legal_api/reports/report.py b/legal-api/src/legal_api/reports/report.py index 45e5fdb8fd..4de77e186e 100644 --- a/legal-api/src/legal_api/reports/report.py +++ b/legal-api/src/legal_api/reports/report.py @@ -78,20 +78,15 @@ def _get_static_report(self): document_type = ReportMeta.static_reports[self._report_key]["documentType"] document: Document = self._filing.documents.filter(Document.type == document_type).first() # DRS-backed keys are "{documentClass}-{documentServiceId}", e.g. "COOP-DS0000101951"; - # legacy Minio keys (UUIDs) and bare DRS ids ("DS...") do not match. if match := re.match(r"^([A-Z]+)-(DS\d+)$", document.file_key or ""): # the DRS applies the certified copy stamp itself for configured combinations # (e.g. COOP-COSD), so DRS-served documents must not be stamped again here from legal_api.services import doc_service drs_response = doc_service.get_document(match.group(2), match.group(1), doc_binary=True) document_data, status = drs_response.content, drs_response.status_code - else: - from legal_api.services import MinioService - minio_response = MinioService.get_file(document.file_key) - document_data, status = minio_response.data, minio_response.status - if self._report_key == "affidavit" and status == HTTPStatus.OK: - # legacy storage never stamps, so the registrar's certification stamp is applied here - document_data = self._certify_uploaded_document(document_data) + elif self._report_key == "affidavit" and status == HTTPStatus.OK: + # legacy storage never stamps, so the registrar's certification stamp is applied here + document_data = self._certify_uploaded_document(document_data) return current_app.response_class( response=document_data, status=status, diff --git a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py index 87714e6f1f..ace494eda4 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py +++ b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py @@ -34,7 +34,7 @@ from legal_api.reports import get_pdf from legal_api.reports.document_service import DocumentService from legal_api.resources.v2.business.bp import bp -from legal_api.services import MinioService, authorized +from legal_api.services import authorized from legal_api.services import doc_service as client_doc_service from legal_api.utils.auth import jwt from legal_api.utils.util import cors_preflight @@ -121,22 +121,14 @@ def get_documents(identifier: str, # noqa: PLR0911, PLR0912 return get_pdf(filing.storage, legal_filing_name) elif file_key and (document := Document.find_by_file_key(file_key)): - if document.filing_id == filing.id: # make sure the file belongs to this filing - # DRS-backed keys are "{documentClass}-{documentServiceId}", e.g. "COOP-DS0000101951"; - # legacy Minio keys (UUIDs) do not match. - if match := re.match(r"^([A-Z]+)-(DS\d+)$", document.file_key): - drs_response = client_doc_service.get_document(match.group(2), match.group(1), doc_binary=True) - return current_app.response_class( - response=drs_response.content, - status=drs_response.status_code, - mimetype=APP_PDF - ) - response = MinioService.get_file(document.file_key) + if document.filing_id == filing.id and (match := re.match(r"^([A-Z]+)-(DS\d+)$", document.file_key)): # make sure the file belongs to this filing + drs_response = client_doc_service.get_document(match.group(2), match.group(1), doc_binary=True) return current_app.response_class( - response=response.data, - status=response.status, + response=drs_response.content, + status=drs_response.status_code, mimetype=APP_PDF ) + return {}, HTTPStatus.NOT_FOUND diff --git a/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py b/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py index bce36fe814..d8694ae699 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py +++ b/legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py @@ -61,7 +61,6 @@ from legal_api.services import ( STAFF_ROLE, SYSTEM_ROLE, - MinioService, RegistrationBootstrapService, authorized, doc_service, @@ -225,7 +224,7 @@ def delete_filings(identifier, filing_id=None): filing.delete() with suppress(Exception): - ListFilingResource.delete_from_minio(filing_type, filing_json) + ListFilingResource.delete_uploaded_documents(filing_type, filing_json) if identifier.startswith("T") and filing.filing_type != Filing.FILINGS["noticeOfWithdrawal"]["name"]: bootstrap = RegistrationBootstrap.find_by_identifier(identifier) @@ -1114,19 +1113,17 @@ def is_future_effective_filing(filing_json: dict) -> bool: @staticmethod def delete_uploaded_file(file_key: str): - """Delete an uploaded file from the DRS or Minio based on the file key shape. + """Delete an uploaded file from the DRS based on the file key shape. DRS-backed keys are "{documentClass}-{documentServiceId}", e.g. "COOP-DS0000101951"; - legacy Minio keys (UUIDs) do not match. """ if re.match(r"^([A-Z]+)-(DS\d+)$", file_key): doc_service.delete_document(Document(file_key=file_key)) - else: - MinioService.delete_file(file_key) + @staticmethod - def delete_from_minio(filing_type: str, filing_json: dict): - """Delete the filing's uploaded files from the DRS or Minio.""" + def delete_uploaded_documents(filing_type: str, filing_json: dict): + """Delete the filing's uploaded files from the DRS.""" if (filing_type == Filing.FILINGS["incorporationApplication"].get("name") and (cooperative := filing_json .get("filing", {}) @@ -1157,7 +1154,7 @@ def delete_from_minio(filing_type: str, filing_json: dict): @staticmethod def delete_continuation_in_files(filing_json: dict): - """Delete continuation in files from minio.""" + """Delete continuation in files from DRS.""" continuation_in = filing_json.get("filing", {}).get("continuationIn", {}) # Delete affidavit file diff --git a/legal-api/src/legal_api/resources/v2/document.py b/legal-api/src/legal_api/resources/v2/document.py index bcbacdf37c..09fa70f0c8 100644 --- a/legal-api/src/legal_api/resources/v2/document.py +++ b/legal-api/src/legal_api/resources/v2/document.py @@ -20,7 +20,6 @@ from business_model.models import Document, Filing from legal_api.services import doc_service -from legal_api.services.minio import MinioService from legal_api.utils.auth import jwt bp = Blueprint("DOCUMENTS2", __name__, url_prefix="/api/v2/documents") @@ -87,12 +86,6 @@ } -@bp.route("//signatures", methods=["GET"]) -@cross_origin() -@jwt.requires_auth -def get_signatures(file_name: str): - """Return a pre-signed URL for the new document.""" - return MinioService.create_signed_put_url(file_name), HTTPStatus.OK def is_draft_filing(file_key: str) -> bool: @@ -104,40 +97,7 @@ def is_draft_filing(file_key: str) -> bool: return filing and filing.status == Filing.Status.DRAFT.value -@bp.route("/", methods=["DELETE"]) -@cross_origin() -@jwt.requires_auth -def delete_minio_document(document_key): - """Delete Minio document based on the provided document key and if it is a draft filing.""" - try: - if is_draft_filing(document_key): - MinioService.delete_file(document_key) - return jsonify({"message": f"File {document_key} deleted successfully."}), HTTPStatus.OK - return jsonify({"message": "Filing is not a draft."}), HTTPStatus.FORBIDDEN - except Exception as e: - current_app.logger.error(f"Error deleting file {document_key}: {e}") - return jsonify( - message=f"Error deleting file {document_key}." - ), HTTPStatus.INTERNAL_SERVER_ERROR - -@bp.route("/", methods=["GET"]) -@cross_origin() -@jwt.requires_auth -def get_minio_document(document_key: str): - """Get the document from Minio.""" - try: - response = MinioService.get_file(document_key) - return current_app.response_class( - response=response.data, - status=response.status, - mimetype="application/pdf" - ) - except Exception as e: - current_app.logger.error(f"Error getting file {document_key}: {e}") - return jsonify( - message=f"Error getting file {document_key}." - ), HTTPStatus.INTERNAL_SERVER_ERROR @bp.route("/client///", methods=["POST"]) diff --git a/legal-api/src/legal_api/services/__init__.py b/legal-api/src/legal_api/services/__init__.py index 51f2ea4f77..1c3a85820a 100644 --- a/legal-api/src/legal_api/services/__init__.py +++ b/legal-api/src/legal_api/services/__init__.py @@ -38,7 +38,6 @@ from .business_details_version import VersionedBusinessDetailsService from .colin import ColinService from .furnishing_documents_service import FurnishingDocumentsService -from .minio import MinioService from .mras_service import MrasService from .naics import NaicsService from .namex import NameXService @@ -71,7 +70,6 @@ "DigitalCredentialsRulesService", "Flags", "FurnishingDocumentsService", - "MinioService", "MrasService", "NaicsService", "NameXService", diff --git a/legal-api/src/legal_api/services/filings/validations/common_validations.py b/legal-api/src/legal_api/services/filings/validations/common_validations.py index 2c089f7279..dffc677f41 100644 --- a/legal-api/src/legal_api/services/filings/validations/common_validations.py +++ b/legal-api/src/legal_api/services/filings/validations/common_validations.py @@ -33,7 +33,7 @@ from business_model.models import Address, Business, PartyRole from legal_api.core.filing import Filing as CoreFiling from legal_api.errors import Error -from legal_api.services import STAFF_ROLE, MinioService, colin, doc_service, flags, namex +from legal_api.services import STAFF_ROLE, colin, doc_service, flags, namex from legal_api.services.permissions import ListActionsPermissionsAllowed, PermissionService from legal_api.services.request_context import get_request_context from legal_api.services.utils import get_str @@ -591,7 +591,7 @@ def validate_pdf(file_key: str, file_key_path: str, verify_paper_size: bool = Tr return None def _get_file_data(file_key: str) -> tuple[bytes, int]: - """Return (file_bytes, file_size) for a file_key, whether DRS-backed or legacy Minio.""" + """Return (file_bytes, file_size) for a DRS-backed file_key.""" enabled_features: list[str] = flags.value("enable-new-feature", []) if "drs-upload" in enabled_features and (match := DRS_KEY_PATTERN.match(file_key)): @@ -601,9 +601,6 @@ def _get_file_data(file_key: str) -> tuple[bytes, int]: raise ValueError(f"DRS get_document failed: status={response.status_code}") return response.content, len(response.content) - file = MinioService.get_file(file_key) - file_info = MinioService.get_file_info(file_key) - return file.data, file_info.size def validate_parties_names(filing_json: dict, filing_type: str, legal_type: str) -> list: """Validate the parties name for COLIN sync.""" diff --git a/legal-api/src/legal_api/services/minio.py b/legal-api/src/legal_api/services/minio.py deleted file mode 100644 index 0baee73b1d..0000000000 --- a/legal-api/src/legal_api/services/minio.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright © 2021 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""This module is a wrapper for Minio.""" -import uuid -from datetime import timedelta - -from flask import current_app -from minio import Minio - - -class MinioService: - """Document Storage class.""" - - @staticmethod - def create_signed_put_url(file_name: str) -> dict: - """Return a pre-signed URL for new doc upload.""" - current_app.logger.debug(f"Creating pre-signed URL for {file_name}") - minio_client: Minio = MinioService._get_client() - file_extension: str = file_name.split(".")[-1] - key = f"{uuid.uuid4()!s}.{file_extension}" - bucket = current_app.config["MINIO_BUCKET_BUSINESSES"] - signed_url_details = { - "preSignedUrl": minio_client.presigned_put_object(bucket, key, timedelta(minutes=5)), - "key": key - } - - return signed_url_details - - @staticmethod - def create_signed_get_url(key: str) -> str: - """Return a pre-signed URL for uploaded document.""" - minio_client: Minio = MinioService._get_client() - current_app.logger.debug(f"Creating pre-signed GET URL for {key}") - bucket = current_app.config["MINIO_BUCKET_BUSINESSES"] - - return minio_client.presigned_get_object(bucket, key, timedelta(hours=1)) - - @staticmethod - def get_file_info(key: str): - """Fetch file info from Minio.""" - minio_client: Minio = MinioService._get_client() - bucket = current_app.config["MINIO_BUCKET_BUSINESSES"] - return minio_client.stat_object(bucket, key) - - @staticmethod - def get_file(key: str): - """ - Fetch file from Minio. - - Example:: - try: - response = minio.get_file(key) - :- `Read data from response.` - finally: - response.close() - response.release_conn() - """ - minio_client: Minio = MinioService._get_client() - bucket = current_app.config["MINIO_BUCKET_BUSINESSES"] - return minio_client.get_object(bucket, key) - - @staticmethod - def delete_file(key: str): - """Delete file from Minio.""" - minio_client: Minio = MinioService._get_client() - bucket = current_app.config["MINIO_BUCKET_BUSINESSES"] - minio_client.remove_object(bucket, key) - - @staticmethod - def _get_client() -> Minio: - """Return a minio client.""" - minio_endpoint = current_app.config["MINIO_ENDPOINT"] - minio_key = current_app.config["MINIO_ACCESS_KEY"] - minio_secret = current_app.config["MINIO_ACCESS_SECRET"] - minio_secure = current_app.config["MINIO_SECURE"] - return Minio(minio_endpoint, access_key=minio_key, secret_key=minio_secret, secure=minio_secure) - - @staticmethod - def put_file(key: str, data: str, length: str): - """Put file to Minio.""" - minio_client: Minio = MinioService._get_client() - bucket = current_app.config["MINIO_BUCKET_BUSINESSES"] - minio_client.put_object(bucket, key, data, length) From 5f07a971833441fd61da6a24b7efaecc4667397b Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Tue, 11 Aug 2026 13:12:46 -0600 Subject: [PATCH 2/5] made chnages to test files --- legal-api/tests/conftest.py | 66 ----------- legal-api/tests/unit/reports/test_report.py | 66 +++-------- .../test_filing_documents.py | 23 +--- .../v2/test_business_filings/test_filings.py | 109 ++++++++---------- .../tests/unit/resources/v2/test_document.py | 9 -- .../filings/validations/test_alteration.py | 2 +- .../validations/test_common_validations.py | 14 +-- .../filings/validations/test_court_order.py | 2 +- .../filings/validations/test_dissolution.py | 7 +- .../test_incorporation_application.py | 4 +- legal-api/tests/unit/services/test_minio.py | 99 ---------------- 11 files changed, 74 insertions(+), 327 deletions(-) delete mode 100644 legal-api/tests/unit/services/test_minio.py diff --git a/legal-api/tests/conftest.py b/legal-api/tests/conftest.py index 060396bdde..928fa58078 100644 --- a/legal-api/tests/conftest.py +++ b/legal-api/tests/conftest.py @@ -25,7 +25,6 @@ from flask import Flask from flask_migrate import Migrate, upgrade from ldclient.integrations.test_data import TestData -from minio.error import S3Error from sqlalchemy import event, text from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql.functions import now as _sqla_now @@ -211,71 +210,6 @@ def restart_savepoint(s, trans): connection.close() -@pytest.fixture() -def minio_server(monkeypatch): - """Create the minio services that the integration tests will use.""" - mock_url = 'https://dummy-minio-url.com/businesses' - minio_mock = Mock() - - def _presigned_url_side_effect(bucket_name, key, *args, **kwargs): - return f'{mock_url}/{key}' - - def _put_object_side_effect(bucket_name, key, data, *args, **kwargs): - # The 'data' argument is a file-like object, read its content. - # Ensure it's read to the end for completeness, but typically only one read is needed. - file_content = data.read() - minio_mock.stored_objects[key] = file_content - return None - - def _get_object_side_effect(bucket_name, key, *args, **kwargs): - if key in minio_mock.stored_objects: - mock_file = Mock() - mock_file.data = minio_mock.stored_objects[key] - return mock_file - raise S3Error("NoSuchKey", "Object does not exist", None, None, None, None) - - def _get_info_side_effect(bucket_name, key, *args, **kwargs): - if key in minio_mock.stored_objects: - mock_file = Mock() - mock_file.size = len(minio_mock.stored_objects[key]) - return mock_file - raise S3Error("NoSuchKey", "Object does not exist", None, None, None, None) - - def _remove_object_side_effect(bucket_name, key, *args, **kwargs): - if key in minio_mock.stored_objects: - del minio_mock.stored_objects[key] - return None - - minio_mock.stored_objects = {} - - minio_mock.presigned_get_object.side_effect = _presigned_url_side_effect - minio_mock.presigned_put_object.side_effect = _presigned_url_side_effect - minio_mock.stat_object.side_effect = _get_info_side_effect - minio_mock.get_object.side_effect = _get_object_side_effect - minio_mock.remove_object.side_effect = _remove_object_side_effect - minio_mock.put_object.side_effect = _put_object_side_effect - - monkeypatch.setattr('legal_api.services.minio.MinioService._get_client', lambda: minio_mock) - with requests_mock.Mocker() as mock: - def _mock_put_side_effect(request, context): - key = request.url.replace(f'{mock_url}/', '', 1) - minio_mock.stored_objects[key] = request.body - context.status_code = HTTPStatus.CREATED - return None - - def _mock_get_side_effect(request, context): - key = request.url.replace(f'{mock_url}/', '', 1) - content = minio_mock.stored_objects.get(key) - if content is not None: - context.status_code = HTTPStatus.OK - return content - else: - raise S3Error("NoSuchKey", "Object does not exist", None, None, None, None) - - mock.put(re.compile(f"{mock_url}.*"), json=_mock_put_side_effect) - mock.get(re.compile(f"{mock_url}.*"), content=_mock_get_side_effect) - - yield minio_mock @pytest.fixture(scope="function") diff --git a/legal-api/tests/unit/reports/test_report.py b/legal-api/tests/unit/reports/test_report.py index 58a79a5edf..d20db6ac67 100644 --- a/legal-api/tests/unit/reports/test_report.py +++ b/legal-api/tests/unit/reports/test_report.py @@ -1225,72 +1225,36 @@ def _make_pdf_bytes(text, pages=1): return buffer.getvalue() -@pytest.mark.parametrize('test_name,file_key,expect_drs', [ - ('drs_key', 'COOP-DS0000101951', True), - ('legacy_minio_key', '3c7aff7b-3351-4911-90fa-402189fdd94d.pdf', False), - ('legacy_bare_drs_id', 'DS0000100800', False), +@pytest.mark.parametrize('test_name,file_key', [ + ('drs_key', 'COOP-DS0000101951'), ]) -def test_get_static_report_drs_dispatch(session, test_name, file_key, expect_drs): - """Assert static report documents are served from the DRS or Minio based on the file key shape (#34300).""" - from legal_api.services import MinioService, doc_service +def test_get_static_report_drs_dispatch(session, test_name, file_key): + """Assert static report documents are served from DRS (#34300).""" + from legal_api.services import doc_service filing = _make_static_report_filing('CP1234567', 'coop_rules', file_key) report = Report(filing) drs_response = MagicMock(content=b'drs-pdf', status_code=HTTPStatus.OK) - minio_response = MagicMock(data=b'minio-pdf', status=HTTPStatus.OK) - with patch.object(doc_service, 'get_document', return_value=drs_response) as mock_drs, \ - patch.object(MinioService, 'get_file', return_value=minio_response) as mock_minio: + with patch.object(doc_service, 'get_document', return_value=drs_response) as mock_drs: response = report.get_pdf(report_type='certifiedRules') - if expect_drs: - mock_drs.assert_called_once_with('DS0000101951', 'COOP', doc_binary=True) - mock_minio.assert_not_called() - assert response.data == b'drs-pdf' - else: - mock_drs.assert_not_called() - mock_minio.assert_called_once_with(file_key) - assert response.data == b'minio-pdf' - - -@pytest.mark.parametrize('test_name,file_key,expect_stamp', [ - # the DRS applies its own certified copy stamp for configured combinations (e.g. COOP-COSD), - # so the api must serve DRS bytes unmodified to avoid double stamping (#34424) - ('drs_backed', 'COOP-DS0000101951', False), - ('minio_backed', '3c7aff7b-3351-4911-90fa-402189fdd94d.pdf', True), -]) -def test_affidavit_static_report_certification(session, test_name, file_key, expect_stamp): - """Assert the registrar's certification stamp is applied only to legacy storage-backed affidavits.""" - import io as _io + mock_drs.assert_called_once_with('DS0000101951', 'COOP', doc_binary=True) + assert response.data == b'drs-pdf' - from pypdf import PdfReader - from legal_api.services import MinioService, doc_service +def test_affidavit_static_report_certification(session): + """Assert DRS-served affidavit bytes pass through untouched (no double stamping, #34424).""" + from legal_api.services import doc_service identifier = 'CP1234567' - filing = _make_static_report_filing(identifier, 'affidavit', file_key) + filing = _make_static_report_filing(identifier, 'affidavit', 'COOP-DS0000101951') # two pages so the stamp can be shown to land on the first page only pdf_bytes = _make_pdf_bytes('Affidavit body', pages=2) drs_response = MagicMock(content=pdf_bytes, status_code=HTTPStatus.OK) - minio_response = MagicMock(data=pdf_bytes, status=HTTPStatus.OK) - with patch.object(doc_service, 'get_document', return_value=drs_response), \ - patch.object(MinioService, 'get_file', return_value=minio_response): + with patch.object(doc_service, 'get_document', return_value=drs_response): response = Report(filing).get_pdf(report_type='affidavit') - if not expect_stamp: - # DRS-served bytes pass through untouched (the DRS stamp, when configured, is already in them) - assert response.get_data() == pdf_bytes - return - stamped = PdfReader(_io.BytesIO(response.get_data())) - text = stamped.get_page(0).extract_text() - assert 'Affidavit body page 1' in text - # The stamp's text half, drawn by create_registrars_stamp. - assert 'Filed on' in text - assert identifier in text - # The "CERTIFIED COPY ... Registrar of Companies" box and the registrar's name are pixels - # inside the registrar_signature_and_text image, not pdf text, so they cannot be asserted - # via text extraction; assert the image itself instead. The source pdf has no images, so - # the single image on page 1 is the stamp, and none on page 2 proves first-page-only. - assert len(stamped.pages[0].images) == 1 - assert len(stamped.pages[1].images) == 0 + # DRS-served bytes pass through untouched (the DRS stamp, when configured, is already in them) + assert response.get_data() == pdf_bytes diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py index 3487be6ed1..488324e293 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py @@ -2167,17 +2167,14 @@ def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library me assert rv_data == expected -@pytest.mark.parametrize('test_name,file_key,expect_drs', [ - ('drs_key', 'CORP-DS0000101951', True), - ('legacy_minio_key', '3c7aff7b-3351-4911-90fa-402189fdd94d.pdf', False), -]) -def test_get_static_document_by_file_key_shape(session, client, jwt, mocker, test_name, file_key, expect_drs): - """Assert static documents are served from the DRS or Minio based on the file key shape.""" +def test_get_static_document_by_file_key_shape(session, client, jwt, mocker): + """Assert static documents are served from DRS.""" from unittest.mock import MagicMock, patch from business_model.models import Document from legal_api.resources.v2.business.business_filings import business_documents + file_key = 'CORP-DS0000101951' identifier = 'CP7654321' business = factory_business(identifier) filing_json = copy.deepcopy(FILING_HEADER) @@ -2189,19 +2186,11 @@ def test_get_static_document_by_file_key_shape(session, client, jwt, mocker, tes mocker.patch.object(business_documents, '_is_document_available', return_value=True) drs_response = MagicMock(content=b'drs-pdf', status_code=HTTPStatus.OK) - minio_response = MagicMock(data=b'minio-pdf', status=HTTPStatus.OK) - with patch.object(business_documents.client_doc_service, 'get_document', return_value=drs_response) as mock_drs, \ - patch.object(business_documents.MinioService, 'get_file', return_value=minio_response) as mock_minio: + with patch.object(business_documents.client_doc_service, 'get_document', return_value=drs_response) as mock_drs: rv = client.get(f'/api/v2/businesses/{identifier}/filings/{filing.id}/documents/static/{file_key}', headers=create_header(jwt, [STAFF_ROLE], identifier, **{'accept': 'application/pdf'})) assert rv.status_code == HTTPStatus.OK - if expect_drs: - mock_drs.assert_called_once_with('DS0000101951', 'CORP', doc_binary=True) - mock_minio.assert_not_called() - assert rv.data == b'drs-pdf' - else: - mock_drs.assert_not_called() - mock_minio.assert_called_once_with(file_key) - assert rv.data == b'minio-pdf' + mock_drs.assert_called_once_with('DS0000101951', 'CORP', doc_binary=True) + assert rv.data == b'drs-pdf' diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py index be8353b971..89d133ba49 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py @@ -28,7 +28,6 @@ import pytest from dateutil.parser import parse from flask import current_app -from minio.error import S3Error from reportlab.lib.pagesizes import letter from business_common.utils.legislation_datetime import LegislationDatetime @@ -48,7 +47,6 @@ from legal_api.resources.v2.business.business_filings.business_filings import ListFilingResource from legal_api.services.authz import BASIC_USER, PUBLIC_USER, STAFF_ROLE from legal_api.services.bootstrap import RegistrationBootstrapService -from legal_api.services.minio import MinioService from registry_schemas.example_data import ( ALTERATION_FILING_TEMPLATE, AMALGAMATION_APPLICATION, @@ -1099,8 +1097,10 @@ def test_delete_draft_now_filing(session, client, jwt): assert 'noticeOfWithdrawal' not in rv.json['filing'] -def test_delete_coop_ia_filing_in_draft_with_file_in_minio(session, client, jwt, minio_server): - """Assert that a draft filing can be deleted.""" +def test_delete_coop_ia_filing_in_draft_with_file_in_drs(session, client, jwt): + """Assert that a draft filing can be deleted and DRS files are removed.""" + from legal_api.resources.v2.business.business_filings import business_filings + identifier = 'T1234567' temp_reg = RegistrationBootstrap() temp_reg._identifier = identifier @@ -1116,8 +1116,8 @@ def test_delete_coop_ia_filing_in_draft_with_file_in_minio(session, client, jwt, 'cooperativeAssociationType': 'CP' } - rules_file_key = _upload_file(letter, invalid=False) - memorandum_file_key = _upload_file(letter, invalid=False) + rules_file_key = 'COOP-DS0000101951' + memorandum_file_key = 'COOP-DS0000101952' filing_json['filing']['incorporationApplication']['cooperative']['rulesFileKey'] = rules_file_key filing_json['filing']['incorporationApplication']['cooperative']['memorandumFileKey'] = memorandum_file_key filing = factory_filing(Business(), filing_json, filing_type='incorporationApplication') @@ -1125,24 +1125,19 @@ def test_delete_coop_ia_filing_in_draft_with_file_in_minio(session, client, jwt, filing.save() headers = create_header(jwt, [STAFF_ROLE], identifier) - with patch.object(RegistrationBootstrapService, 'deregister_bootstrap', return_value=HTTPStatus.OK): - with patch.object(RegistrationBootstrapService, 'delete_bootstrap', return_value=HTTPStatus.OK): - rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) + with patch.object(RegistrationBootstrapService, 'deregister_bootstrap', return_value=HTTPStatus.OK), \ + patch.object(RegistrationBootstrapService, 'delete_bootstrap', return_value=HTTPStatus.OK), \ + patch.object(business_filings.doc_service, 'delete_document') as mock_drs: + rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) - assert rv.status_code == HTTPStatus.OK - try: - MinioService.get_file_info(rules_file_key) - except S3Error as ex: - assert ex.code == 'NoSuchKey' + assert rv.status_code == HTTPStatus.OK + assert mock_drs.call_count == 2 - try: - MinioService.get_file_info(memorandum_file_key) - except S3Error as ex: - assert ex.code == 'NoSuchKey' +def test_delete_continuation_in_filing_with_authorization_files_in_draft(session, client, jwt): + """Assert that a draft continuationIn filing can be deleted and authorization files are removed from DRS.""" + from legal_api.resources.v2.business.business_filings import business_filings -def test_delete_continuation_in_filing_with_authorization_files_in_draft(session, client, jwt, minio_server): - """Assert that a draft continuationIn filing can be deleted and authorization files are removed from Minio.""" identifier = 'CP1234568' b = factory_business(identifier) @@ -1155,26 +1150,26 @@ def test_delete_continuation_in_filing_with_authorization_files_in_draft(session "files": [] } } - file_key_1 = _upload_file(letter, invalid=False) - file_key_2 = _upload_file(letter, invalid=False) + file_key_1 = 'CORP-DS0000101951' + file_key_2 = 'CORP-DS0000101952' filing_json['filing']['continuationIn']['authorization']['files'] = [ {"fileKey": file_key_1}, {"fileKey": file_key_2} ] filing = factory_filing(b, filing_json, filing_type='continuationIn') headers = create_header(jwt, [STAFF_ROLE], identifier) - rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) + + with patch.object(business_filings.doc_service, 'delete_document') as mock_drs: + rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) assert rv.status_code == HTTPStatus.OK - for file_key in [file_key_1, file_key_2]: - try: - MinioService.get_file_info(file_key) - except S3Error as ex: - assert ex.code == 'NoSuchKey' + assert mock_drs.call_count == 2 -def test_delete_continuation_in_filing_with_affidavit_in_draft(session, client, jwt, minio_server): - """Assert that a draft continuationIn filing can be deleted and the affidavit file is removed from Minio.""" +def test_delete_continuation_in_filing_with_affidavit_in_draft(session, client, jwt): + """Assert that a draft continuationIn filing can be deleted and the affidavit file is removed from DRS.""" + from legal_api.resources.v2.business.business_filings import business_filings + identifier = 'CP1234567' b = factory_business(identifier) @@ -1187,21 +1182,22 @@ def test_delete_continuation_in_filing_with_affidavit_in_draft(session, client, "files": [] } } - file_key = _upload_file(letter, invalid=False) + file_key = 'CORP-DS0000101953' filing_json['filing']['continuationIn']['foreignJurisdiction']['affidavitFileKey'] = file_key filing = factory_filing(b, filing_json, filing_type='continuationIn') headers = create_header(jwt, [STAFF_ROLE], identifier) - rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) + + with patch.object(business_filings.doc_service, 'delete_document') as mock_drs: + rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) assert rv.status_code == HTTPStatus.OK - try: - MinioService.get_file_info(file_key) - except S3Error as ex: - assert ex.code == 'NoSuchKey' + mock_drs.assert_called_once() -def test_delete_dissolution_filing_in_draft_with_file_in_minio(session, client, jwt, minio_server): - """Assert that a draft filing can be deleted.""" +def test_delete_dissolution_filing_in_draft_with_file_in_drs(session, client, jwt): + """Assert that a draft filing can be deleted and DRS file is removed.""" + from legal_api.resources.v2.business.business_filings import business_filings + identifier = 'CP7654321' b = factory_business(identifier) @@ -1209,17 +1205,16 @@ def test_delete_dissolution_filing_in_draft_with_file_in_minio(session, client, filing_json['filing']['header']['name'] = 'dissolution' filing_json['filing']['business']['legalType'] = 'CP' filing_json['filing']['dissolution'] = copy.deepcopy(DISSOLUTION) - file_key = _upload_file(letter, invalid=False) + file_key = 'COOP-DS0000101954' filing_json['filing']['dissolution']['affidavitFileKey'] = file_key filing = factory_filing(b, filing_json, filing_type='dissolution') headers = create_header(jwt, [STAFF_ROLE], identifier) - rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) + + with patch.object(business_filings.doc_service, 'delete_document') as mock_drs: + rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) assert rv.status_code == HTTPStatus.OK - try: - MinioService.get_file_info(file_key) - except S3Error as ex: - assert ex.code == 'NoSuchKey' + mock_drs.assert_called_once() def test_delete_filing_block_completed(session, client, jwt): @@ -2465,26 +2460,16 @@ def test_ta(session, requests_mock, client, jwt, monkeypatch, test_name, legal_t assert rv.json[0]['message'] == 'Permission Denied - transition filing is currently not available for this user and/or account.' -@pytest.mark.parametrize('test_name,file_key,expect_drs', [ - ('drs_key', 'COOP-DS0000101951', True), - ('legacy_minio_key', '3c7aff7b-3351-4911-90fa-402189fdd94d.pdf', False), - ('legacy_bare_drs_id', 'DS0000100800', False), -]) -def test_delete_uploaded_file_dispatch(session, test_name, file_key, expect_drs): - """Assert uploaded files are deleted from the DRS or Minio based on the file key shape.""" +def test_delete_uploaded_file_dispatch(session): + """Assert uploaded files are deleted from DRS.""" from legal_api.resources.v2.business.business_filings import business_filings - with patch.object(business_filings.doc_service, 'delete_document') as mock_drs, \ - patch.object(MinioService, 'delete_file') as mock_minio: + file_key = 'COOP-DS0000101951' + with patch.object(business_filings.doc_service, 'delete_document') as mock_drs: ListFilingResource.delete_uploaded_file(file_key) - if expect_drs: - mock_drs.assert_called_once() - assert mock_drs.call_args[0][0].file_key == file_key - mock_minio.assert_not_called() - else: - mock_drs.assert_not_called() - mock_minio.assert_called_once_with(file_key) + mock_drs.assert_called_once() + assert mock_drs.call_args[0][0].file_key == file_key def test_delete_dissolution_filing_in_draft_with_drs_file(session, client, jwt): @@ -2502,11 +2487,9 @@ def test_delete_dissolution_filing_in_draft_with_drs_file(session, client, jwt): filing = factory_filing(b, filing_json, filing_type='dissolution') headers = create_header(jwt, [STAFF_ROLE], identifier) - with patch.object(business_filings.doc_service, 'delete_document') as mock_drs, \ - patch.object(MinioService, 'delete_file') as mock_minio: + with patch.object(business_filings.doc_service, 'delete_document') as mock_drs: rv = client.delete(f'/api/v2/businesses/{identifier}/filings/{filing.id}', headers=headers) assert rv.status_code == HTTPStatus.OK mock_drs.assert_called_once() assert mock_drs.call_args[0][0].file_key == file_key - mock_minio.assert_not_called() diff --git a/legal-api/tests/unit/resources/v2/test_document.py b/legal-api/tests/unit/resources/v2/test_document.py index 2d6621636c..316ee12cce 100644 --- a/legal-api/tests/unit/resources/v2/test_document.py +++ b/legal-api/tests/unit/resources/v2/test_document.py @@ -110,15 +110,6 @@ ] -def test_documents_signature_get_returns_200(client, jwt, session, minio_server): # pylint:disable=unused-argument - """Assert get documents/filename/signatures endpoint returns 200.""" - headers = create_header(jwt, [STAFF_ROLE]) - file_name = 'test_file.jpeg' - rv = client.get(f'/api/v2/documents/{file_name}/signatures', headers=headers, content_type='application/json') - - assert rv.status_code == HTTPStatus.OK - assert 'key' in rv.json and 'preSignedUrl' in rv.json - @pytest.mark.parametrize('desc,filing_type,entity_type,doc_type', TEST_CLIENT_POST_DATA) def test_create_client_document(session, client, jwt, desc, filing_type, entity_type, doc_type): diff --git a/legal-api/tests/unit/services/filings/validations/test_alteration.py b/legal-api/tests/unit/services/filings/validations/test_alteration.py index 62dffbc694..d7dbccda15 100644 --- a/legal-api/tests/unit/services/filings/validations/test_alteration.py +++ b/legal-api/tests/unit/services/filings/validations/test_alteration.py @@ -513,7 +513,7 @@ def test_alteration_share_classes_optional(session): }]), ]) @patch.object(PermissionService, 'check_user_permission', MagicMock(return_value=None)) -def test_validate_cooperative_documents(session, mocker, minio_server, test_name, key, scenario, expected_code, +def test_validate_cooperative_documents(session, mocker, test_name, key, scenario, expected_code, expected_msg): """Assert that validator validates cooperative documents correctly.""" identifier = 'CP1234567' diff --git a/legal-api/tests/unit/services/filings/validations/test_common_validations.py b/legal-api/tests/unit/services/filings/validations/test_common_validations.py index 1a6f1e36d4..6653985ebf 100644 --- a/legal-api/tests/unit/services/filings/validations/test_common_validations.py +++ b/legal-api/tests/unit/services/filings/validations/test_common_validations.py @@ -2329,14 +2329,7 @@ def test_validate_foreign_jurisdiction(session, test_name, foreign_jurisdiction, ] ) def test_get_file_data_from_drs(session, monkeypatch, file_key, expected_class, expected_id): - """Test that DRS document keys retrieve content from DRS instead of MinIO.""" - - monkeypatch.setattr( - 'legal_api.services.flags.value', - lambda flag, default=None: - ["drs-upload"] - if flag == "enable-new-feature" else default - ) + """Test that DRS document keys retrieve content from DRS.""" monkeypatch.setattr( 'legal_api.services.filings.validations.common_validations.doc_service.get_document', @@ -2351,11 +2344,6 @@ def test_get_file_data_from_drs(session, monkeypatch, file_key, expected_class, )() ) - monkeypatch.setattr( - 'legal_api.services.filings.validations.common_validations.MinioService.get_file', - lambda _: pytest.fail("MinIO should not be called for DRS documents") - ) - data, size = _get_file_data(file_key) assert data == b'test pdf content' diff --git a/legal-api/tests/unit/services/filings/validations/test_court_order.py b/legal-api/tests/unit/services/filings/validations/test_court_order.py index 433207a5a3..0061506c9f 100644 --- a/legal-api/tests/unit/services/filings/validations/test_court_order.py +++ b/legal-api/tests/unit/services/filings/validations/test_court_order.py @@ -64,7 +64,7 @@ def test_court_orders(session, test_status, expected_code, expected_msg): 'error': 'Document must be set to fit onto 8.5” x 11” letter-size paper.', 'path': file_key_path}]) ]) -def test_court_order_file(session, minio_server, test_name, expected_code, expected_msg): +def test_court_order_file(session, test_name, expected_code, expected_msg): """Assert valid court order.""" business = factory_business('BC1234567') filing = copy.deepcopy(COURT_ORDER_FILING_TEMPLATE) diff --git a/legal-api/tests/unit/services/filings/validations/test_dissolution.py b/legal-api/tests/unit/services/filings/validations/test_dissolution.py index a141d70dca..e40fc9f3c8 100644 --- a/legal-api/tests/unit/services/filings/validations/test_dissolution.py +++ b/legal-api/tests/unit/services/filings/validations/test_dissolution.py @@ -28,7 +28,7 @@ from reportlab.lib.pagesizes import letter from business_model.models import Business -from legal_api.services import MinioService, flags +from legal_api.services import flags from legal_api.services.filings.validations import dissolution from legal_api.services.filings.validations.dissolution import validate from tests.unit.services.filings.test_utils import _upload_file @@ -351,7 +351,7 @@ def test_dissolution_special_resolution(session, test_name, legal_type, dissolut }]), ] ) -def test_dissolution_affidavit(session, minio_server, test_name, legal_type, dissolution_type, key, scenario, +def test_dissolution_affidavit(session, test_name, legal_type, dissolution_type, key, scenario, identifier, expected_code, expected_msg): # pylint: disable=too-many-arguments """Assert that an affidavit can be validated.""" # setup @@ -391,9 +391,6 @@ def test_dissolution_affidavit(session, minio_server, test_name, legal_type, dis else: assert err is None - # Cleanup - if file_key := filing['filing']['dissolution'].get('affidavitFileKey', None): - MinioService.delete_file(file_key) @pytest.mark.parametrize( diff --git a/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py b/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py index 64b2c92cef..f7dae08538 100644 --- a/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py +++ b/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py @@ -655,7 +655,7 @@ def test_validate_name_request(session, mocker, test_name, legal_type, expected_ ) ]) @pytest.mark.parametrize('cp_flag_enabled', [True, False]) -def test_validate_incorporation_role(session, minio_server, mocker, test_name, +def test_validate_incorporation_role(session, mocker, test_name, legal_type, parties, expected_code, expected_msg, cp_flag_enabled): """Assert that incorporation parties roles can be validated.""" @@ -1631,7 +1631,7 @@ def test_validate_incorporation_effective_date(session, mocker, test_name, effec 'path': memorandum_file_key_path }]), ]) -def test_validate_cooperative_documents(session, mocker, minio_server, test_name, key, scenario, expected_code, +def test_validate_cooperative_documents(session, mocker, test_name, key, scenario, expected_code, expected_msg): """Assert that validator validates cooperative documents correctly.""" filing_json = copy.deepcopy(INCORPORATION_FILING_TEMPLATE) diff --git a/legal-api/tests/unit/services/test_minio.py b/legal-api/tests/unit/services/test_minio.py deleted file mode 100644 index f321537d34..0000000000 --- a/legal-api/tests/unit/services/test_minio.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright © 2021 Province of British Columbia -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests for the Minio service. - -Test suite to ensure that the Minio service routines are working as expected. -""" -import os - -import requests -from minio.error import S3Error - -from legal_api.services import MinioService - -from .test_pdf_service import _create_pdf_file - - -def test_create_signed_put_url(session, minio_server): # pylint:disable=unused-argument - """Assert that the a PUT url can be pre-signed.""" - file_name = 'cooperative-test.pdf' - signed_url = MinioService.create_signed_put_url(file_name) - assert signed_url - assert signed_url.get('key').endswith('.pdf') - - -def test_create_signed_get_url(session, minio_server, tmpdir): # pylint:disable=unused-argument - """Assert that a GET url can be pre-signed.""" - key = _upload_file(tmpdir) - pre_signed_get = MinioService.create_signed_get_url(key) - assert pre_signed_get - get_response = requests.get(pre_signed_get) - assert get_response - - -def test_get_file_info(session, minio_server, tmpdir): # pylint:disable=unused-argument - """Assert that we can retrieve a file info.""" - key = _upload_file(tmpdir) - file_info = MinioService.get_file_info(key) - assert file_info - - -def test_get_file(session, minio_server, tmpdir): # pylint:disable=unused-argument - """Assert that we can retrieve a file.""" - key = _upload_file(tmpdir) - get_response = MinioService.get_file(key) - assert get_response - - -def test_delete_file(session, minio_server, tmpdir): # pylint:disable=unused-argument - """Assert that a file can be deleted.""" - key = _upload_file(tmpdir) - MinioService.delete_file(key) - - try: - MinioService.get_file_info(key) - except S3Error as ex: - assert ex.code == 'NoSuchKey' - - -def _upload_file(tmpdir): - d = tmpdir.mkdir('subdir') - fh = d.join('cooperative-test.pdf') - fh.write('Test File') - filename = os.path.join(fh.dirname, fh.basename) - - test_file = open(filename, 'rb') - files = {'upload_file': test_file} - file_name = fh.basename - signed_url = MinioService.create_signed_put_url(file_name) - key = signed_url.get('key') - pre_signed_put = signed_url.get('preSignedUrl') - requests.put(pre_signed_put, files=files) - return key - - -def test_put_file(session, minio_server, tmpdir): # pylint:disable=unused-argument - """Assert that a file can be replaced.""" - key = _upload_file(tmpdir) - - pdf_file = _create_pdf_file() - # Replace previous file with this pdf file - MinioService.put_file(key, pdf_file, pdf_file.getbuffer().nbytes) - - try: - file = MinioService.get_file(key) - pdf_file.seek(0) - assert file.data == pdf_file.read() - except S3Error as ex: - assert ex.code == 'NoSuchKey' From 3a92ee73556cb7cd50cfbb7843ab626871643b16 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Tue, 11 Aug 2026 21:39:03 -0600 Subject: [PATCH 3/5] updated test cases --- .../filings/validations/common_validations.py | 4 +-- .../v2/test_business_filings/test_filings.py | 1 - .../tests/unit/services/filings/test_utils.py | 36 ++++++++++++++----- .../filings/validations/test_admin_freeze.py | 1 - .../filings/validations/test_alteration.py | 5 +-- .../filings/validations/test_court_order.py | 5 +-- .../filings/validations/test_dissolution.py | 5 +-- .../test_incorporation_application.py | 8 +++-- 8 files changed, 43 insertions(+), 22 deletions(-) diff --git a/legal-api/src/legal_api/services/filings/validations/common_validations.py b/legal-api/src/legal_api/services/filings/validations/common_validations.py index dffc677f41..72e69487b6 100644 --- a/legal-api/src/legal_api/services/filings/validations/common_validations.py +++ b/legal-api/src/legal_api/services/filings/validations/common_validations.py @@ -592,9 +592,7 @@ def validate_pdf(file_key: str, file_key_path: str, verify_paper_size: bool = Tr def _get_file_data(file_key: str) -> tuple[bytes, int]: """Return (file_bytes, file_size) for a DRS-backed file_key.""" - enabled_features: list[str] = flags.value("enable-new-feature", []) - - if "drs-upload" in enabled_features and (match := DRS_KEY_PATTERN.match(file_key)): + if match := DRS_KEY_PATTERN.match(file_key): doc_class, drs_id = match.group(1), match.group(2) response = doc_service.get_document(drs_id, doc_class, doc_binary=True) if not response.ok: diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py index 89d133ba49..299065abe4 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py @@ -88,7 +88,6 @@ factory_pending_filing, factory_user, ) -from tests.unit.services.filings.test_utils import _upload_file from tests.unit.services.utils import create_header diff --git a/legal-api/tests/unit/services/filings/test_utils.py b/legal-api/tests/unit/services/filings/test_utils.py index 88fbb18790..08f0b215f3 100644 --- a/legal-api/tests/unit/services/filings/test_utils.py +++ b/legal-api/tests/unit/services/filings/test_utils.py @@ -13,17 +13,19 @@ # limitations under the License. """Test suite to ensure the Common Utilities are working correctly.""" import io +import random from datetime import date -import requests from hypothesis import example, given from hypothesis.strategies import text from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas -from legal_api.services import MinioService from legal_api.services.utils import get_date, get_str +# In-memory store mapping DRS file key -> PDF bytes, used by _upload_file and drs_document_mock. +_drs_store: dict = {} + @given(f=text(), p=text()) @example(f={'filing': {'header': {'date': '2001-08-05'}}}, @@ -50,13 +52,31 @@ def test_get_str(f, p): def _upload_file(page_size, invalid): - signed_url = MinioService.create_signed_put_url('cooperative-test.pdf') - key = signed_url.get('key') - pre_signed_put = signed_url.get('preSignedUrl') + """Create a PDF, store it in _drs_store keyed by a DRS file key, and return the key.""" + pdf_data = _create_pdf_file(page_size, invalid).read() + drs_id = f'DS{random.randint(1000000000, 9999999999)}' + file_key = f'COOP-{drs_id}' + _drs_store[file_key] = pdf_data + return file_key + + +def mock_drs_get_document(monkeypatch): + """Monkeypatch doc_service.get_document to serve PDF bytes from _drs_store. + + Call this at the start of any test that uses _upload_file and then calls validate_pdf. + Works with pytest monkeypatch fixture. + """ + from unittest.mock import MagicMock + + import legal_api.services.filings.validations.common_validations as cv + + def _side_effect(drs_id, doc_class, doc_binary=True): + file_key = f'{doc_class}-{drs_id}' + pdf_bytes = _drs_store.get(file_key, b'') + return MagicMock(ok=True, content=pdf_bytes) - requests.put(pre_signed_put, data=_create_pdf_file(page_size, invalid).read(), - headers={'Content-Type': 'application/octet-stream'}) - return key + monkeypatch.setattr(cv, 'doc_service', + MagicMock(get_document=MagicMock(side_effect=_side_effect))) def _create_pdf_file(page_size, invalid): diff --git a/legal-api/tests/unit/services/filings/validations/test_admin_freeze.py b/legal-api/tests/unit/services/filings/validations/test_admin_freeze.py index f7cff09148..eb12dfa3f9 100644 --- a/legal-api/tests/unit/services/filings/validations/test_admin_freeze.py +++ b/legal-api/tests/unit/services/filings/validations/test_admin_freeze.py @@ -22,7 +22,6 @@ from legal_api.services.filings.validations.admin_freeze import validate from tests.unit.models import factory_business -from tests.unit.services.filings.test_utils import _upload_file from tests.unit.services.filings.validations import lists_are_equal diff --git a/legal-api/tests/unit/services/filings/validations/test_alteration.py b/legal-api/tests/unit/services/filings/validations/test_alteration.py index d7dbccda15..9ceb889428 100644 --- a/legal-api/tests/unit/services/filings/validations/test_alteration.py +++ b/legal-api/tests/unit/services/filings/validations/test_alteration.py @@ -30,7 +30,7 @@ from legal_api.services.permissions import ListActionsPermissionsAllowed, PermissionService from registry_schemas.example_data import ALTERATION_FILING_TEMPLATE from tests.unit.models import factory_business -from tests.unit.services.filings.test_utils import _upload_file +from tests.unit.services.filings.test_utils import _upload_file, mock_drs_get_document from tests.unit.services.filings.validations import lists_are_equal @@ -513,9 +513,10 @@ def test_alteration_share_classes_optional(session): }]), ]) @patch.object(PermissionService, 'check_user_permission', MagicMock(return_value=None)) -def test_validate_cooperative_documents(session, mocker, test_name, key, scenario, expected_code, +def test_validate_cooperative_documents(session, monkeypatch, mocker, test_name, key, scenario, expected_code, expected_msg): """Assert that validator validates cooperative documents correctly.""" + mock_drs_get_document(monkeypatch) identifier = 'CP1234567' business = factory_business(identifier) diff --git a/legal-api/tests/unit/services/filings/validations/test_court_order.py b/legal-api/tests/unit/services/filings/validations/test_court_order.py index 0061506c9f..17a0bbb461 100644 --- a/legal-api/tests/unit/services/filings/validations/test_court_order.py +++ b/legal-api/tests/unit/services/filings/validations/test_court_order.py @@ -22,7 +22,7 @@ from legal_api.services.filings.validations.court_order import validate from tests.unit.models import factory_business -from tests.unit.services.filings.test_utils import _upload_file +from tests.unit.services.filings.test_utils import _upload_file, mock_drs_get_document from tests.unit.services.filings.validations import lists_are_equal @@ -64,8 +64,9 @@ def test_court_orders(session, test_status, expected_code, expected_msg): 'error': 'Document must be set to fit onto 8.5” x 11” letter-size paper.', 'path': file_key_path}]) ]) -def test_court_order_file(session, test_name, expected_code, expected_msg): +def test_court_order_file(session, monkeypatch, test_name, expected_code, expected_msg): """Assert valid court order.""" + mock_drs_get_document(monkeypatch) business = factory_business('BC1234567') filing = copy.deepcopy(COURT_ORDER_FILING_TEMPLATE) diff --git a/legal-api/tests/unit/services/filings/validations/test_dissolution.py b/legal-api/tests/unit/services/filings/validations/test_dissolution.py index e40fc9f3c8..9e35b8d773 100644 --- a/legal-api/tests/unit/services/filings/validations/test_dissolution.py +++ b/legal-api/tests/unit/services/filings/validations/test_dissolution.py @@ -31,7 +31,7 @@ from legal_api.services import flags from legal_api.services.filings.validations import dissolution from legal_api.services.filings.validations.dissolution import validate -from tests.unit.services.filings.test_utils import _upload_file +from tests.unit.services.filings.test_utils import _upload_file, mock_drs_get_document from tests.unit.services.filings.validations import create_party, create_party_address, lists_are_equal @@ -351,9 +351,10 @@ def test_dissolution_special_resolution(session, test_name, legal_type, dissolut }]), ] ) -def test_dissolution_affidavit(session, test_name, legal_type, dissolution_type, key, scenario, +def test_dissolution_affidavit(session, monkeypatch, test_name, legal_type, dissolution_type, key, scenario, identifier, expected_code, expected_msg): # pylint: disable=too-many-arguments """Assert that an affidavit can be validated.""" + mock_drs_get_document(monkeypatch) # setup business = Business(identifier=identifier, legal_type=legal_type) diff --git a/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py b/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py index f7dae08538..00969d8cab 100644 --- a/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py +++ b/legal-api/tests/unit/services/filings/validations/test_incorporation_application.py @@ -29,7 +29,7 @@ from business_model.models import Business from legal_api.services.filings import validate -from tests.unit.services.filings.test_utils import _upload_file +from tests.unit.services.filings.test_utils import _upload_file, mock_drs_get_document from . import create_party, create_party_address, lists_are_equal, create_officer from tests import not_github_ci @@ -655,10 +655,11 @@ def test_validate_name_request(session, mocker, test_name, legal_type, expected_ ) ]) @pytest.mark.parametrize('cp_flag_enabled', [True, False]) -def test_validate_incorporation_role(session, mocker, test_name, +def test_validate_incorporation_role(session, monkeypatch, mocker, test_name, legal_type, parties, expected_code, expected_msg, cp_flag_enabled): """Assert that incorporation parties roles can be validated.""" + mock_drs_get_document(monkeypatch) mocker.patch.object(flags, 'value', return_value=["incorporationApplication-completingParty"] if cp_flag_enabled else []) filing_json = copy.deepcopy(INCORPORATION_FILING_TEMPLATE) @@ -1631,9 +1632,10 @@ def test_validate_incorporation_effective_date(session, mocker, test_name, effec 'path': memorandum_file_key_path }]), ]) -def test_validate_cooperative_documents(session, mocker, test_name, key, scenario, expected_code, +def test_validate_cooperative_documents(session, monkeypatch, mocker, test_name, key, scenario, expected_code, expected_msg): """Assert that validator validates cooperative documents correctly.""" + mock_drs_get_document(monkeypatch) filing_json = copy.deepcopy(INCORPORATION_FILING_TEMPLATE) filing_json['filing']['header'] = {'name': incorporation_application_name, 'date': '2019-04-08', 'certifiedBy': 'full name', 'authorizationReceived': True, From 714e7c66c9bc3aa364d390fc8a81896201763af6 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Tue, 11 Aug 2026 21:45:24 -0600 Subject: [PATCH 4/5] version bump --- legal-api/poetry.lock | 18 +----------------- legal-api/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/legal-api/poetry.lock b/legal-api/poetry.lock index 485bfaf5c6..f5fada0e02 100644 --- a/legal-api/poetry.lock +++ b/legal-api/poetry.lock @@ -2265,22 +2265,6 @@ files = [ {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] -[[package]] -name = "minio" -version = "7.0.2" -description = "MinIO Python SDK for Amazon S3 Compatible Cloud Storage" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "minio-7.0.2-py3-none-any.whl", hash = "sha256:0e69575a0b1b64bce9f158b52b5914953f902a21032966d070ab0d14ceef8962"}, - {file = "minio-7.0.2.tar.gz", hash = "sha256:f2f6022cfe4694d946972efef2a752f87d08cc030940faa50a640088772953c8"}, -] - -[package.dependencies] -certifi = "*" -urllib3 = "*" - [[package]] name = "multidict" version = "6.7.1" @@ -4436,4 +4420,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.13,<3.14" -content-hash = "38eb06e163c276a193e17597b91b0b3b3a58f445a1d060dfda9f4d68d813871e" +content-hash = "ae5d62d00a5b3878147fc84676d7da2a7a5399bc4e2d03bdbdedca7bd9eb32bb" diff --git a/legal-api/pyproject.toml b/legal-api/pyproject.toml index 2b95b20c4a..a0d7693bc3 100644 --- a/legal-api/pyproject.toml +++ b/legal-api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "legal-api" -version = "3.1.10" +version = "3.1.11" description = "" authors = [ {name = "thor",email = "1042854+thorwolpert@users.noreply.github.com"} From ddb73ffb33ae093a5bb7ed599046e2602de9651a Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Wed, 12 Aug 2026 09:06:21 -0600 Subject: [PATCH 5/5] removed dead code --- legal-api/src/legal_api/reports/report.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/legal-api/src/legal_api/reports/report.py b/legal-api/src/legal_api/reports/report.py index 4de77e186e..f78b644869 100644 --- a/legal-api/src/legal_api/reports/report.py +++ b/legal-api/src/legal_api/reports/report.py @@ -84,30 +84,12 @@ def _get_static_report(self): from legal_api.services import doc_service drs_response = doc_service.get_document(match.group(2), match.group(1), doc_binary=True) document_data, status = drs_response.content, drs_response.status_code - elif self._report_key == "affidavit" and status == HTTPStatus.OK: - # legacy storage never stamps, so the registrar's certification stamp is applied here - document_data = self._certify_uploaded_document(document_data) return current_app.response_class( response=document_data, status=status, mimetype="application/pdf" ) - def _certify_uploaded_document(self, document_bytes: bytes) -> bytes: - """Apply the registrar's certification stamp when the document is downloaded. - - Documents are never stamped on upload; the stamp is applied to each - served copy and the stored original is left unchanged. - """ - from legal_api.services import PdfService - from legal_api.services.pdf_service import RegistrarStampData - business = self._business - if not business and self._filing.business_id: - business = Business.find_by_internal_id(self._filing.business_id) - identifier = business.identifier if business else self._filing.temp_reg - stamp_data = RegistrarStampData(self._filing.filing_date, identifier) - return PdfService().create_certified_copy(document_bytes, stamp_data).read() - def _get_report(self, regenerate: bool = False): # Try to get report from DRS first: get to here if duplicate UI request before refreshing filing documents. if self._filing.business_id: