From 3b00c9c029a0ee3b58da2998cda73b9b6f6ca98b Mon Sep 17 00:00:00 2001 From: Robert Segal Date: Wed, 31 Dec 2025 10:33:45 -0700 Subject: [PATCH] Added e2e tests for custom ledgers endpoints --- e2e_config.test.json | 3 + mpt_api_client/constants.py | 1 + .../resources/billing/custom_ledger_upload.py | 31 ----- .../resources/billing/custom_ledgers.py | 96 ++++++++++++--- pyproject.toml | 3 +- tests/data/test_custom_ledger.xlsx | Bin 0 -> 14768 bytes .../custom_ledger/attachment/conftest.py | 24 ++++ .../test_async_custom_ledger_attachment.py | 110 ++++++++++++++++++ .../test_sync_custom_ledger_attachment.py | 105 +++++++++++++++++ .../billing/custom_ledger/charge/conftest.py | 11 ++ .../test_async_custom_ledger_charges.py | 45 +++++++ .../charge/test_sync_custom_ledger_charges.py | 45 +++++++ tests/e2e/billing/custom_ledger/conftest.py | 53 +++++++++ .../custom_ledger/test_async_custom_ledger.py | 89 ++++++++++++++ .../custom_ledger/test_sync_custom_ledger.py | 87 ++++++++++++++ .../billing/test_custom_ledger_upload.py | 50 -------- .../resources/billing/test_custom_ledgers.py | 51 ++++++-- 17 files changed, 694 insertions(+), 110 deletions(-) delete mode 100644 mpt_api_client/resources/billing/custom_ledger_upload.py create mode 100644 tests/data/test_custom_ledger.xlsx create mode 100644 tests/e2e/billing/custom_ledger/attachment/conftest.py create mode 100644 tests/e2e/billing/custom_ledger/attachment/test_async_custom_ledger_attachment.py create mode 100644 tests/e2e/billing/custom_ledger/attachment/test_sync_custom_ledger_attachment.py create mode 100644 tests/e2e/billing/custom_ledger/charge/conftest.py create mode 100644 tests/e2e/billing/custom_ledger/charge/test_async_custom_ledger_charges.py create mode 100644 tests/e2e/billing/custom_ledger/charge/test_sync_custom_ledger_charges.py create mode 100644 tests/e2e/billing/custom_ledger/conftest.py create mode 100644 tests/e2e/billing/custom_ledger/test_async_custom_ledger.py create mode 100644 tests/e2e/billing/custom_ledger/test_sync_custom_ledger.py delete mode 100644 tests/unit/resources/billing/test_custom_ledger_upload.py diff --git a/e2e_config.test.json b/e2e_config.test.json index a61e5d35..ae9ec468 100644 --- a/e2e_config.test.json +++ b/e2e_config.test.json @@ -12,6 +12,9 @@ "accounts.user.id": "USR-9673-3314", "accounts.user_group.id": "UGR-6822-0561", "audit.record.id": "AUD-3748-4760-1006-3938", + "billing.custom_ledger.attachment.id": "CLA-1777-7485", + "billing.custom_ledger.charge.id": "CHG-2665-3524-0000-0000-0020", + "billing.custom_ledger.id": "CLE-2665-3524", "billing.journal.attachment.id": "JOA-6425-9776", "billing.journal.id": "BJO-6562-0928", "catalog.authorization.id": "AUT-9288-6146", diff --git a/mpt_api_client/constants.py b/mpt_api_client/constants.py index 438cc0cb..c0b1552d 100644 --- a/mpt_api_client/constants.py +++ b/mpt_api_client/constants.py @@ -1 +1,2 @@ APPLICATION_JSON = "application/json" +MIMETYPE_EXCEL_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" diff --git a/mpt_api_client/resources/billing/custom_ledger_upload.py b/mpt_api_client/resources/billing/custom_ledger_upload.py deleted file mode 100644 index 47351df2..00000000 --- a/mpt_api_client/resources/billing/custom_ledger_upload.py +++ /dev/null @@ -1,31 +0,0 @@ -from mpt_api_client.http import AsyncService, Service -from mpt_api_client.http.mixins import AsyncFilesOperationsMixin, FilesOperationsMixin -from mpt_api_client.models import Model - - -class CustomLedgerUpload(Model): - """Custom Ledger Upload resource.""" - - -class CustomLedgerUploadServiceConfig: - """Custom Ledger Upload service configuration.""" - - _endpoint = "/public/v1/billing/custom-ledgers/{custom_ledger_id}/upload" - _model_class = CustomLedgerUpload - _collection_key = "data" - - -class CustomLedgerUploadService( - FilesOperationsMixin[CustomLedgerUpload], - Service[CustomLedgerUpload], - CustomLedgerUploadServiceConfig, -): - """Custom Ledger Upload service.""" - - -class AsyncCustomLedgerUploadService( - AsyncFilesOperationsMixin[CustomLedgerUpload], - AsyncService[CustomLedgerUpload], - CustomLedgerUploadServiceConfig, -): - """Async Custom Ledger Upload service.""" diff --git a/mpt_api_client/resources/billing/custom_ledgers.py b/mpt_api_client/resources/billing/custom_ledgers.py index e5253f2f..1d1b45f7 100644 --- a/mpt_api_client/resources/billing/custom_ledgers.py +++ b/mpt_api_client/resources/billing/custom_ledgers.py @@ -1,3 +1,8 @@ +import pathlib +from typing import cast +from urllib.parse import urljoin + +from mpt_api_client.constants import MIMETYPE_EXCEL_XLSX from mpt_api_client.http import AsyncService, Service from mpt_api_client.http.mixins import ( AsyncCollectionMixin, @@ -5,6 +10,7 @@ CollectionMixin, ManagedResourceMixin, ) +from mpt_api_client.http.types import FileContent, FileTypes from mpt_api_client.models import Model from mpt_api_client.resources.billing.custom_ledger_attachments import ( AsyncCustomLedgerAttachmentsService, @@ -14,10 +20,6 @@ AsyncCustomLedgerChargesService, CustomLedgerChargesService, ) -from mpt_api_client.resources.billing.custom_ledger_upload import ( - AsyncCustomLedgerUploadService, - CustomLedgerUploadService, -) from mpt_api_client.resources.billing.mixins import AcceptableMixin, AsyncAcceptableMixin @@ -31,6 +33,8 @@ class CustomLedgersServiceConfig: _endpoint = "/public/v1/billing/custom-ledgers" _model_class = CustomLedger _collection_key = "data" + _upload_file_key = "file" + _upload_data_key = "id" class CustomLedgersService( @@ -42,6 +46,41 @@ class CustomLedgersService( ): """Custom Ledgers service.""" + def upload(self, custom_ledger_id: str, file: FileTypes) -> CustomLedger: + """Upload custom ledger file. + + Args: + custom_ledger_id: Custom Ledger ID. + file: Custom Ledger file. + + Returns: + CustomLedger: Created resource. + """ + files: dict[str, FileTypes] = {} + + filename = pathlib.Path(getattr(file, "name", "uploaded_file.xlsx")).name + + # Mimetype is set to Excel XLSX to prevent 415 response from the server + files[self._upload_file_key] = ( + filename, + cast("FileContent", file), + MIMETYPE_EXCEL_XLSX, + ) # UNUSED type: ignore[attr-defined] + files[self._upload_data_key] = custom_ledger_id # UNUSED type: ignore + + path = urljoin(f"{self.path}/", f"{custom_ledger_id}/upload") + + response = self.http_client.request( # UNUSED type: ignore[attr-defined] + "post", + path, # UNUSED type: ignore[attr-defined] + files=files, + force_multipart=True, + ) + + return self._model_class.from_response( + response + ) # UNUSED type: ignore[attr-defined, no-any-return] + def charges(self, custom_ledger_id: str) -> CustomLedgerChargesService: """Return custom ledger charges service.""" return CustomLedgerChargesService( @@ -49,13 +88,6 @@ def charges(self, custom_ledger_id: str) -> CustomLedgerChargesService: endpoint_params={"custom_ledger_id": custom_ledger_id}, ) - def upload(self, custom_ledger_id: str) -> CustomLedgerUploadService: - """Get the Custom Ledger Upload service.""" - return CustomLedgerUploadService( - http_client=self.http_client, - endpoint_params={"custom_ledger_id": custom_ledger_id}, - ) - def attachments(self, custom_ledger_id: str) -> CustomLedgerAttachmentsService: """Return custom ledger attachments service.""" return CustomLedgerAttachmentsService( @@ -73,6 +105,41 @@ class AsyncCustomLedgersService( ): """Async Custom Ledgers service.""" + async def upload(self, custom_ledger_id: str, file: FileTypes) -> CustomLedger: + """Upload custom ledger file. + + Args: + custom_ledger_id: Custom Ledger ID. + file: Custom Ledger file. + + Returns: + CustomLedger: Created resource. + """ + files: dict[str, FileTypes] = {} + + filename = pathlib.Path(getattr(file, "name", "uploaded_file.xlsx")).name + + # Mimetype is set to Excel XLSX to prevent 415 response from the server + files[self._upload_file_key] = ( + filename, + cast("FileContent", file), + MIMETYPE_EXCEL_XLSX, + ) # UNUSED type: ignore[attr-defined] + files[self._upload_data_key] = custom_ledger_id # UNUSED type: ignore + + path = urljoin(f"{self.path}/", f"{custom_ledger_id}/upload") + + response = await self.http_client.request( # UNUSED type: ignore[attr-defined] + "post", + path, # UNUSED type: ignore[attr-defined] + files=files, + force_multipart=True, + ) + + return self._model_class.from_response( + response + ) # UNUSED type: ignore[attr-defined, no-any-return] + def charges(self, custom_ledger_id: str) -> AsyncCustomLedgerChargesService: """Return custom ledger charges service.""" return AsyncCustomLedgerChargesService( @@ -80,13 +147,6 @@ def charges(self, custom_ledger_id: str) -> AsyncCustomLedgerChargesService: endpoint_params={"custom_ledger_id": custom_ledger_id}, ) - def upload(self, custom_ledger_id: str) -> AsyncCustomLedgerUploadService: - """Get the Async Custom Ledger Upload service.""" - return AsyncCustomLedgerUploadService( - http_client=self.http_client, - endpoint_params={"custom_ledger_id": custom_ledger_id}, - ) - def attachments(self, custom_ledger_id: str) -> AsyncCustomLedgerAttachmentsService: """Return custom ledger attachments service.""" return AsyncCustomLedgerAttachmentsService( diff --git a/pyproject.toml b/pyproject.toml index e8d1c03d..9b7623c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,13 +119,14 @@ per-file-ignores = [ "mpt_api_client/mpt_client.py: WPS214 WPS235", "mpt_api_client/resources/*: WPS215", "mpt_api_client/resources/accounts/*.py: WPS202 WPS215 WPS214 WPS235 WPS453", - "mpt_api_client/resources/billing/*.py: WPS202 WPS204 WPS214 WPS215 WPS235", + "mpt_api_client/resources/billing/*.py: WPS202 WPS204 WPS214 WPS215 WPS235 WPS110", "mpt_api_client/resources/catalog/*.py: WPS110 WPS214 WPS215 WPS235", "mpt_api_client/resources/catalog/mixins.py: WPS110 WPS202 WPS214 WPS215 WPS235", "mpt_api_client/resources/catalog/products.py: WPS204 WPS214 WPS215 WPS235", "mpt_api_client/resources/commerce/*.py: WPS235 WPS215", "mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214", "tests/e2e/accounts/*.py: WPS430 WPS202", + "tests/e2e/billing/*.py: WPS202 WPS421 WPS118", "tests/e2e/catalog/*.py: WPS202 WPS421", "tests/e2e/catalog/items/*.py: WPS110 WPS202", "tests/e2e/commerce/*.py: WPS202 WPS204 WPS453", diff --git a/tests/data/test_custom_ledger.xlsx b/tests/data/test_custom_ledger.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..960c48b62cfd96f0b7b86271306862bf3da8573f GIT binary patch literal 14768 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/billing/custom_ledger/attachment/conftest.py b/tests/e2e/billing/custom_ledger/attachment/conftest.py new file mode 100644 index 00000000..b90b1842 --- /dev/null +++ b/tests/e2e/billing/custom_ledger/attachment/conftest.py @@ -0,0 +1,24 @@ +import pytest + + +@pytest.fixture +def custom_ledger_attachment_id(e2e_config): + return e2e_config["billing.custom_ledger.attachment.id"] + + +@pytest.fixture +def invalid_custom_ledger_attachment_id(): + return "CLA-0000-0000" + + +@pytest.fixture +def custom_ledger_attachment_factory(): + def factory( + name: str = "E2E Created Custom Ledger Attachment", + ): + return { + "name": name, + "description": name, + } + + return factory diff --git a/tests/e2e/billing/custom_ledger/attachment/test_async_custom_ledger_attachment.py b/tests/e2e/billing/custom_ledger/attachment/test_async_custom_ledger_attachment.py new file mode 100644 index 00000000..081b3fe0 --- /dev/null +++ b/tests/e2e/billing/custom_ledger/attachment/test_async_custom_ledger_attachment.py @@ -0,0 +1,110 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +async def created_custom_ledger_attachment( + async_mpt_ops, custom_ledger_attachment_factory, custom_ledger_id, pdf_fd +): + new_custom_ledger_attachment_request_data = custom_ledger_attachment_factory( + name="E2E Created Custom Ledger Attachment", + ) + custom_ledger_attachments = async_mpt_ops.billing.custom_ledgers.attachments(custom_ledger_id) + + created_attachment = await custom_ledger_attachments.create( + new_custom_ledger_attachment_request_data, file=pdf_fd + ) + + yield created_attachment + + try: + await custom_ledger_attachments.delete(created_attachment.id) + except MPTAPIError as error: + print(f"TEARDOWN - Unable to delete custom ledger attachment: {error.title}") # noqa: WPS421 + + +@pytest.fixture +def custom_ledger_attachments(async_mpt_ops, custom_ledger_id): + return async_mpt_ops.billing.custom_ledgers.attachments(custom_ledger_id) + + +async def test_get_custom_ledger_attachment_by_id( + custom_ledger_attachments, custom_ledger_attachment_id +): + result = await custom_ledger_attachments.get(custom_ledger_attachment_id) + + assert result is not None + + +async def test_get_custom_ledger_attachment_by_id_not_found( + custom_ledger_attachments, invalid_custom_ledger_attachment_id +): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await custom_ledger_attachments.get(invalid_custom_ledger_attachment_id) + + +async def test_list_custom_ledger_attachments(custom_ledger_attachments): + limit = 10 + + result = await custom_ledger_attachments.fetch_page(limit=limit) + + assert len(result) > 0 + + +async def test_filter_custom_ledger_attachments( + custom_ledger_attachments, custom_ledger_attachment_id +): + select_fields = ["-price"] + filtered_attachments = ( + custom_ledger_attachments.filter(RQLQuery(id=custom_ledger_attachment_id)) + .filter(RQLQuery(name="test_custom_ledger.xlsx")) + .select(*select_fields) + ) + + result = [attachment async for attachment in filtered_attachments.iterate()] + + assert len(result) == 1 + + +def test_create_billing_custom_ledger_attachment(created_custom_ledger_attachment): + result = created_custom_ledger_attachment + + assert result is not None + + +async def test_update_billing_custom_ledger_attachment( + custom_ledger_attachments, created_custom_ledger_attachment +): + updated_name = "E2E Updated Custom Ledger Attachment" + + update_data = { + "name": updated_name, + } + + updated_attachment = await custom_ledger_attachments.update( + created_custom_ledger_attachment.id, + update_data, + ) + + assert updated_attachment.name == updated_name + + +async def test_delete_billing_custom_ledger_attachment( + custom_ledger_attachments, created_custom_ledger_attachment +): + result = created_custom_ledger_attachment + + await custom_ledger_attachments.delete(result.id) + + +async def test_download_billing_custom_ledger_attachment( + custom_ledger_attachments, created_custom_ledger_attachment +): + result = await custom_ledger_attachments.download(created_custom_ledger_attachment.id) + + assert result.file_contents is not None + assert result.filename is not None diff --git a/tests/e2e/billing/custom_ledger/attachment/test_sync_custom_ledger_attachment.py b/tests/e2e/billing/custom_ledger/attachment/test_sync_custom_ledger_attachment.py new file mode 100644 index 00000000..c7ab5401 --- /dev/null +++ b/tests/e2e/billing/custom_ledger/attachment/test_sync_custom_ledger_attachment.py @@ -0,0 +1,105 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +def created_custom_ledger_attachment( + mpt_ops, custom_ledger_attachment_factory, custom_ledger_id, pdf_fd +): + new_custom_ledger_attachment_request_data = custom_ledger_attachment_factory( + name="E2E Created Custom Ledger Attachment", + ) + custom_ledger_attachments = mpt_ops.billing.custom_ledgers.attachments(custom_ledger_id) + + created_attachment = custom_ledger_attachments.create( + new_custom_ledger_attachment_request_data, file=pdf_fd + ) + + yield created_attachment + + try: + custom_ledger_attachments.delete(created_attachment.id) + except MPTAPIError as error: + print(f"TEARDOWN - Unable to delete custom ledger attachment: {error.title}") # noqa: WPS421 + + +@pytest.fixture +def custom_ledger_attachments(mpt_ops, custom_ledger_id): + return mpt_ops.billing.custom_ledgers.attachments(custom_ledger_id) + + +def test_get_custom_ledger_attachment_by_id(custom_ledger_attachments, custom_ledger_attachment_id): + result = custom_ledger_attachments.get(custom_ledger_attachment_id) + + assert result is not None + + +def test_get_custom_ledger_attachment_by_id_not_found( + custom_ledger_attachments, invalid_custom_ledger_attachment_id +): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + custom_ledger_attachments.get(invalid_custom_ledger_attachment_id) + + +def test_list_custom_ledger_attachments(custom_ledger_attachments): + limit = 10 + + result = custom_ledger_attachments.fetch_page(limit=limit) + + assert len(result) > 0 + + +def test_filter_custom_ledger_attachments(custom_ledger_attachments, custom_ledger_attachment_id): + select_fields = ["-price"] + filtered_attachments = ( + custom_ledger_attachments.filter(RQLQuery(id=custom_ledger_attachment_id)) + .filter(RQLQuery(name="test_custom_ledger.xlsx")) + .select(*select_fields) + ) + + result = list(filtered_attachments.iterate()) + + assert len(result) == 1 + + +def test_create_billing_custom_ledger_attachment(created_custom_ledger_attachment): + result = created_custom_ledger_attachment + + assert result is not None + + +def test_update_billing_custom_ledger_attachment( + custom_ledger_attachments, created_custom_ledger_attachment +): + updated_name = "E2E Updated Custom Ledger Attachment" + update_data = { + "name": updated_name, + } + + result = custom_ledger_attachments.update( + created_custom_ledger_attachment.id, + update_data, + ) + + assert result is not None + + +def test_delete_billing_custom_ledger_attachment( + custom_ledger_attachments, created_custom_ledger_attachment +): + result = created_custom_ledger_attachment + + custom_ledger_attachments.delete(result.id) + + +def test_download_billing_custom_ledger_attachment( + custom_ledger_attachments, created_custom_ledger_attachment +): + result = custom_ledger_attachments.download(created_custom_ledger_attachment.id) + + assert result.file_contents is not None + assert result.filename is not None diff --git a/tests/e2e/billing/custom_ledger/charge/conftest.py b/tests/e2e/billing/custom_ledger/charge/conftest.py new file mode 100644 index 00000000..7199a377 --- /dev/null +++ b/tests/e2e/billing/custom_ledger/charge/conftest.py @@ -0,0 +1,11 @@ +import pytest + + +@pytest.fixture +def custom_ledger_charge_id(e2e_config): + return e2e_config["billing.custom_ledger.charge.id"] + + +@pytest.fixture +def invalid_custom_ledger_charge_id(): + return "CHG-0000-0000-0000-0000-0000" diff --git a/tests/e2e/billing/custom_ledger/charge/test_async_custom_ledger_charges.py b/tests/e2e/billing/custom_ledger/charge/test_async_custom_ledger_charges.py new file mode 100644 index 00000000..a5fb3293 --- /dev/null +++ b/tests/e2e/billing/custom_ledger/charge/test_async_custom_ledger_charges.py @@ -0,0 +1,45 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +def custom_ledger_charges(async_mpt_ops, custom_ledger_id): + return async_mpt_ops.billing.custom_ledgers.charges(custom_ledger_id) + + +async def test_get_custom_ledger_charge_by_id(custom_ledger_charges, custom_ledger_charge_id): + result = await custom_ledger_charges.get(custom_ledger_charge_id) + + assert result is not None + + +async def test_get_custom_ledger_charge_by_id_not_found( + custom_ledger_charges, invalid_custom_ledger_charge_id +): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await custom_ledger_charges.get(invalid_custom_ledger_charge_id) + + +async def test_list_custom_ledger_charges(custom_ledger_charges): + limit = 10 + + result = await custom_ledger_charges.fetch_page(limit=limit) + + assert len(result) > 0 + + +async def test_filter_custom_ledger_charges(custom_ledger_charges, custom_ledger_charge_id): + select_fields = ["-price"] + filtered_charges = ( + custom_ledger_charges.filter(RQLQuery(id=custom_ledger_charge_id)) + .filter(RQLQuery(description__value2="Description 2")) + .select(*select_fields) + ) + + result = [charge async for charge in filtered_charges.iterate()] + + assert len(result) == 1 diff --git a/tests/e2e/billing/custom_ledger/charge/test_sync_custom_ledger_charges.py b/tests/e2e/billing/custom_ledger/charge/test_sync_custom_ledger_charges.py new file mode 100644 index 00000000..8db1bc4c --- /dev/null +++ b/tests/e2e/billing/custom_ledger/charge/test_sync_custom_ledger_charges.py @@ -0,0 +1,45 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +def custom_ledger_charges(mpt_ops, custom_ledger_id): + return mpt_ops.billing.custom_ledgers.charges(custom_ledger_id) + + +def test_get_custom_ledger_charge_by_id(custom_ledger_charges, custom_ledger_charge_id): + result = custom_ledger_charges.get(custom_ledger_charge_id) + + assert result is not None + + +def test_get_custom_ledger_charge_by_id_not_found( + custom_ledger_charges, invalid_custom_ledger_charge_id +): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + custom_ledger_charges.get(invalid_custom_ledger_charge_id) + + +def test_list_custom_ledger_charges(custom_ledger_charges): + limit = 10 + + result = custom_ledger_charges.fetch_page(limit=limit) + + assert len(result) > 0 + + +def test_filter_custom_ledger_charges(custom_ledger_charges, custom_ledger_charge_id): + select_fields = ["-price"] + filtered_charges = ( + custom_ledger_charges.filter(RQLQuery(id=custom_ledger_charge_id)) + .filter(RQLQuery(description__value2="Description 2")) + .select(*select_fields) + ) + + result = list(filtered_charges.iterate()) + + assert len(result) == 1 diff --git a/tests/e2e/billing/custom_ledger/conftest.py b/tests/e2e/billing/custom_ledger/conftest.py new file mode 100644 index 00000000..6ebefbae --- /dev/null +++ b/tests/e2e/billing/custom_ledger/conftest.py @@ -0,0 +1,53 @@ +import pathlib + +import pytest +from freezegun import freeze_time + + +@pytest.fixture +def custom_ledger_id(e2e_config): + return e2e_config["billing.custom_ledger.id"] + + +@pytest.fixture +def invalid_custom_ledger_id(): + return "CLD-0000-0000" + + +@pytest.fixture +@freeze_time("2025-12-01T10:00:00.000Z") +def custom_ledger_factory( + seller_id, + account_id, +): + def factory( + name: str = "E2E Created Custom Ledger", + notes: str = "E2E Created Custom Ledger", + ): + return { + "name": name, + "seller": {"id": seller_id}, + "vendor": {"id": account_id}, + "price": { + "currency": { + "purchase": "USD", + "sale": "USD", + }, + }, + "notes": notes, + "billingStartDate": "2025-12-01T07:00:00.000Z", + "billingEndDate": "2026-11-30T07:00:00.000Z", + "externalIds": {}, + } + + return factory + + +@pytest.fixture +def billing_custom_ledger_fd(): + file_path = pathlib.Path("tests/data/test_custom_ledger.xlsx").resolve() + fd = file_path.open("rb") + try: + yield fd + finally: + fd.close() diff --git a/tests/e2e/billing/custom_ledger/test_async_custom_ledger.py b/tests/e2e/billing/custom_ledger/test_async_custom_ledger.py new file mode 100644 index 00000000..162837d1 --- /dev/null +++ b/tests/e2e/billing/custom_ledger/test_async_custom_ledger.py @@ -0,0 +1,89 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +async def created_custom_ledger(async_mpt_ops, custom_ledger_factory): + new_custom_ledger_request_data = custom_ledger_factory() + + created_custom_ledger = await async_mpt_ops.billing.custom_ledgers.create( + new_custom_ledger_request_data + ) + + yield created_custom_ledger + + try: + await async_mpt_ops.billing.custom_ledgers.delete(created_custom_ledger.id) + except MPTAPIError as error: + print(f"TEARDOWN - Unable to delete custom ledger: {error.title}") + + +async def test_get_custom_ledger_by_id(async_mpt_ops, custom_ledger_id): + result = await async_mpt_ops.billing.custom_ledgers.get(custom_ledger_id) + + assert result is not None + + +async def test_list_custom_ledgers(async_mpt_ops): + limit = 10 + + result = await async_mpt_ops.billing.custom_ledgers.fetch_page(limit=limit) + + assert len(result) > 0 + + +async def test_get_custom_ledger_by_id_not_found(async_mpt_ops, invalid_custom_ledger_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await async_mpt_ops.billing.custom_ledgers.get(invalid_custom_ledger_id) + + +async def test_filter_custom_ledgers(async_mpt_ops, custom_ledger_id): + select_fields = ["-price"] + filtered_custom_ledgers = ( + async_mpt_ops.billing.custom_ledgers.filter(RQLQuery(id=custom_ledger_id)) + .filter(RQLQuery(name="E2E Seeded Custom Ledger")) + .select(*select_fields) + ) + + result = [custom_ledger async for custom_ledger in filtered_custom_ledgers.iterate()] + + assert len(result) == 1 + + +def test_create_custom_ledger(created_custom_ledger): + result = created_custom_ledger + + assert result is not None + + +async def test_update_custom_ledger(async_mpt_ops, created_custom_ledger): + name = "E2E Updated Custom Ledger" + update_data = { + "name": name, + } + + result = await async_mpt_ops.billing.custom_ledgers.update( + created_custom_ledger.id, + update_data, + ) + + assert result is not None + + +async def test_delete_custom_ledger(async_mpt_ops, created_custom_ledger): + result = created_custom_ledger + + await async_mpt_ops.billing.custom_ledgers.delete(result.id) + + +async def test_upload_custom_ledger(async_mpt_ops, created_custom_ledger, billing_custom_ledger_fd): + result = await async_mpt_ops.billing.custom_ledgers.upload( + custom_ledger_id=created_custom_ledger.id, + file=billing_custom_ledger_fd, + ) + + assert result is not None diff --git a/tests/e2e/billing/custom_ledger/test_sync_custom_ledger.py b/tests/e2e/billing/custom_ledger/test_sync_custom_ledger.py new file mode 100644 index 00000000..5e537b30 --- /dev/null +++ b/tests/e2e/billing/custom_ledger/test_sync_custom_ledger.py @@ -0,0 +1,87 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +def created_custom_ledger(mpt_ops, custom_ledger_factory): + new_custom_ledger_request_data = custom_ledger_factory() + + created_custom_ledger = mpt_ops.billing.custom_ledgers.create(new_custom_ledger_request_data) + + yield created_custom_ledger + + try: + mpt_ops.billing.custom_ledgers.delete(created_custom_ledger.id) + except MPTAPIError as error: + print(f"TEARDOWN - Unable to delete custom ledger: {error.title}") + + +def test_get_custom_ledger_by_id(mpt_ops, custom_ledger_id): + result = mpt_ops.billing.custom_ledgers.get(custom_ledger_id) + + assert result is not None + + +def test_list_custom_ledgers(mpt_ops): + limit = 10 + + result = mpt_ops.billing.custom_ledgers.fetch_page(limit=limit) + + assert len(result) > 0 + + +def test_get_custom_ledger_by_id_not_found(mpt_ops, invalid_custom_ledger_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + mpt_ops.billing.custom_ledgers.get(invalid_custom_ledger_id) + + +def test_filter_custom_ledgers(mpt_ops, custom_ledger_id): + select_fields = ["-price"] + filtered_custom_ledgers = ( + mpt_ops.billing.custom_ledgers.filter(RQLQuery(id=custom_ledger_id)) + .filter(RQLQuery(name="E2E Seeded Custom Ledger")) + .select(*select_fields) + ) + + result = list(filtered_custom_ledgers.iterate()) + + assert len(result) == 1 + + +def test_create_custom_ledger(created_custom_ledger): + result = created_custom_ledger + + assert result is not None + + +def test_update_custom_ledger(mpt_ops, created_custom_ledger): + name = "E2E Updated Custom Ledger" + update_data = { + "name": name, + } + + result = mpt_ops.billing.custom_ledgers.update( + created_custom_ledger.id, + update_data, + ) + + assert result is not None + + +def test_delete_custom_ledger(mpt_ops, created_custom_ledger): + result = created_custom_ledger + + mpt_ops.billing.custom_ledgers.delete(result.id) + + +def test_upload_custom_ledger(mpt_ops, created_custom_ledger, billing_custom_ledger_fd): + result = mpt_ops.billing.custom_ledgers.upload( + custom_ledger_id=created_custom_ledger.id, + file=billing_custom_ledger_fd, + ) + + assert result is not None diff --git a/tests/unit/resources/billing/test_custom_ledger_upload.py b/tests/unit/resources/billing/test_custom_ledger_upload.py deleted file mode 100644 index 9d0a7255..00000000 --- a/tests/unit/resources/billing/test_custom_ledger_upload.py +++ /dev/null @@ -1,50 +0,0 @@ -import pytest - -from mpt_api_client.resources.billing.custom_ledger_upload import ( - AsyncCustomLedgerUploadService, - CustomLedgerUploadService, -) - - -@pytest.fixture -def custom_ledger_upload_service(http_client): - return CustomLedgerUploadService( - http_client=http_client, endpoint_params={"custom_ledger_id": "LDG-0000-0001"} - ) - - -@pytest.fixture -def async_custom_ledger_upload_service(http_client): - return AsyncCustomLedgerUploadService( - http_client=http_client, endpoint_params={"custom_ledger_id": "LDG-0000-0001"} - ) - - -def test_endpoint(custom_ledger_upload_service): - result = custom_ledger_upload_service.path == ( - "/public/v1/billing/custom-ledgers/LDG-0000-0001/upload" - ) - - assert result is True - - -def test_async_endpoint(async_custom_ledger_upload_service): - result = async_custom_ledger_upload_service.path == ( - "/public/v1/billing/custom-ledgers/LDG-0000-0001/upload" - ) - - assert result is True - - -@pytest.mark.parametrize("method", ["create"]) -def test_mixins_present(custom_ledger_upload_service, method): - result = hasattr(custom_ledger_upload_service, method) - - assert result is True - - -@pytest.mark.parametrize("method", ["create"]) -def test_async_mixins_present(async_custom_ledger_upload_service, method): - result = hasattr(async_custom_ledger_upload_service, method) - - assert result is True diff --git a/tests/unit/resources/billing/test_custom_ledgers.py b/tests/unit/resources/billing/test_custom_ledgers.py index e44b9443..ca141c37 100644 --- a/tests/unit/resources/billing/test_custom_ledgers.py +++ b/tests/unit/resources/billing/test_custom_ledgers.py @@ -1,4 +1,6 @@ +import httpx import pytest +import respx from mpt_api_client.resources.billing.custom_ledger_attachments import ( AsyncCustomLedgerAttachmentsService, @@ -8,10 +10,6 @@ AsyncCustomLedgerChargesService, CustomLedgerChargesService, ) -from mpt_api_client.resources.billing.custom_ledger_upload import ( - AsyncCustomLedgerUploadService, - CustomLedgerUploadService, -) from mpt_api_client.resources.billing.custom_ledgers import ( AsyncCustomLedgersService, CustomLedgersService, @@ -24,18 +22,22 @@ def custom_ledgers_service(http_client): @pytest.fixture -def async_custom_ledgers_service(http_client): - return AsyncCustomLedgersService(http_client=http_client) +def async_custom_ledgers_service(async_http_client): + return AsyncCustomLedgersService(http_client=async_http_client) -@pytest.mark.parametrize("method", ["get", "create", "update", "delete", "accept", "queue"]) +@pytest.mark.parametrize( + "method", ["get", "create", "update", "delete", "accept", "queue", "upload"] +) def test_mixins_present(custom_ledgers_service, method): result = hasattr(custom_ledgers_service, method) assert result is True -@pytest.mark.parametrize("method", ["get", "create", "update", "delete", "accept", "queue"]) +@pytest.mark.parametrize( + "method", ["get", "create", "update", "delete", "accept", "queue", "upload"] +) def test_async_mixins_present(async_custom_ledgers_service, method): result = hasattr(async_custom_ledgers_service, method) @@ -46,7 +48,6 @@ def test_async_mixins_present(async_custom_ledgers_service, method): ("service_method", "expected_service_class"), [ ("charges", CustomLedgerChargesService), - ("upload", CustomLedgerUploadService), ("attachments", CustomLedgerAttachmentsService), ], ) @@ -61,7 +62,6 @@ def test_property_services(custom_ledgers_service, service_method, expected_serv ("service_method", "expected_service_class"), [ ("charges", AsyncCustomLedgerChargesService), - ("upload", AsyncCustomLedgerUploadService), ("attachments", AsyncCustomLedgerAttachmentsService), ], ) @@ -72,3 +72,34 @@ def test_async_property_services( assert isinstance(result, expected_service_class) assert result.endpoint_params == {"custom_ledger_id": "LDG-0000-0001"} + + +def test_upload(custom_ledgers_service, tmp_path): + file_path = tmp_path / "test_upload.xlsx" + file_path.write_bytes(b"Test content") + with file_path.open("rb") as file_obj, respx.mock: + mock_route = respx.post( + "https://api.example.com/public/v1/billing/custom-ledgers/LDG-0000-0001/upload" + ).mock(return_value=httpx.Response(200, json={"result": "ok"})) + + result = custom_ledgers_service.upload(custom_ledger_id="LDG-0000-0001", file=file_obj) + + assert mock_route.called + assert result is not None + + +async def test_async_upload(async_custom_ledgers_service, tmp_path): + file_path = tmp_path / "test_upload.xlsx" + file_path.write_bytes(b"Test content") + with file_path.open("rb") as file_obj, respx.mock: + mock_route = respx.post( + "https://api.example.com/public/v1/billing/custom-ledgers/LDG-0000-0001/upload" + ).mock(return_value=httpx.Response(200, json={"result": "ok"})) + + result = await async_custom_ledgers_service.upload( + custom_ledger_id="LDG-0000-0001", + file=file_obj, + ) + + assert mock_route.called + assert result is not None