Skip to content

Commit 541ea30

Browse files
authored
[MPT-14927] Added e2e tests for custom ledgers endpoints (#187)
Added e2e tests for custom ledgers, its charges, and its attachments endpoints <!-- This is an auto-generated comment: release notes by coderabbit.ai --> Closes [MPT-14927](https://softwareone.atlassian.net/browse/MPT-14927) **Changes:** - Added comprehensive end-to-end tests for custom ledgers API endpoints, including separate test suites for custom ledger resources, their charges, and attachments (both sync and async variants) - Refactored custom ledger file upload functionality by removing the separate `CustomLedgerUpload` service class and consolidating the upload method directly into the `CustomLedgersService`, simplifying the API surface - Updated `CustomLedgersService.upload()` to accept a file parameter and perform direct file uploads with multipart form data to the `{path}/{custom_ledger_id}/upload` endpoint - Added `MIMETYPE_EXCEL_XLSX` constant for Excel file handling - Added pytest fixtures for custom ledger e2e testing, including factories for generating test data and identifiers for valid and invalid resources - Updated unit tests to reflect the removal of the `CustomLedgerUpload` service class and the new `upload()` method signature - Updated linting configuration to exclude specific rule violations in e2e test files - Added test data configuration entries for custom ledger, charge, and attachment identifiers to support e2e testing <!-- end of auto-generated comment: release notes by coderabbit.ai --> [MPT-14927]: https://softwareone.atlassian.net/browse/MPT-14927?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2 parents 46a898d + 3b00c9c commit 541ea30

17 files changed

+694
-110
lines changed

e2e_config.test.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
"accounts.user.id": "USR-9673-3314",
1313
"accounts.user_group.id": "UGR-6822-0561",
1414
"audit.record.id": "AUD-3748-4760-1006-3938",
15+
"billing.custom_ledger.attachment.id": "CLA-1777-7485",
16+
"billing.custom_ledger.charge.id": "CHG-2665-3524-0000-0000-0020",
17+
"billing.custom_ledger.id": "CLE-2665-3524",
1518
"billing.journal.attachment.id": "JOA-6425-9776",
1619
"billing.journal.id": "BJO-6562-0928",
1720
"catalog.authorization.id": "AUT-9288-6146",

mpt_api_client/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
APPLICATION_JSON = "application/json"
2+
MIMETYPE_EXCEL_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"

mpt_api_client/resources/billing/custom_ledger_upload.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

mpt_api_client/resources/billing/custom_ledgers.py

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
import pathlib
2+
from typing import cast
3+
from urllib.parse import urljoin
4+
5+
from mpt_api_client.constants import MIMETYPE_EXCEL_XLSX
16
from mpt_api_client.http import AsyncService, Service
27
from mpt_api_client.http.mixins import (
38
AsyncCollectionMixin,
49
AsyncManagedResourceMixin,
510
CollectionMixin,
611
ManagedResourceMixin,
712
)
13+
from mpt_api_client.http.types import FileContent, FileTypes
814
from mpt_api_client.models import Model
915
from mpt_api_client.resources.billing.custom_ledger_attachments import (
1016
AsyncCustomLedgerAttachmentsService,
@@ -14,10 +20,6 @@
1420
AsyncCustomLedgerChargesService,
1521
CustomLedgerChargesService,
1622
)
17-
from mpt_api_client.resources.billing.custom_ledger_upload import (
18-
AsyncCustomLedgerUploadService,
19-
CustomLedgerUploadService,
20-
)
2123
from mpt_api_client.resources.billing.mixins import AcceptableMixin, AsyncAcceptableMixin
2224

2325

@@ -31,6 +33,8 @@ class CustomLedgersServiceConfig:
3133
_endpoint = "/public/v1/billing/custom-ledgers"
3234
_model_class = CustomLedger
3335
_collection_key = "data"
36+
_upload_file_key = "file"
37+
_upload_data_key = "id"
3438

3539

3640
class CustomLedgersService(
@@ -42,20 +46,48 @@ class CustomLedgersService(
4246
):
4347
"""Custom Ledgers service."""
4448

49+
def upload(self, custom_ledger_id: str, file: FileTypes) -> CustomLedger:
50+
"""Upload custom ledger file.
51+
52+
Args:
53+
custom_ledger_id: Custom Ledger ID.
54+
file: Custom Ledger file.
55+
56+
Returns:
57+
CustomLedger: Created resource.
58+
"""
59+
files: dict[str, FileTypes] = {}
60+
61+
filename = pathlib.Path(getattr(file, "name", "uploaded_file.xlsx")).name
62+
63+
# Mimetype is set to Excel XLSX to prevent 415 response from the server
64+
files[self._upload_file_key] = (
65+
filename,
66+
cast("FileContent", file),
67+
MIMETYPE_EXCEL_XLSX,
68+
) # UNUSED type: ignore[attr-defined]
69+
files[self._upload_data_key] = custom_ledger_id # UNUSED type: ignore
70+
71+
path = urljoin(f"{self.path}/", f"{custom_ledger_id}/upload")
72+
73+
response = self.http_client.request( # UNUSED type: ignore[attr-defined]
74+
"post",
75+
path, # UNUSED type: ignore[attr-defined]
76+
files=files,
77+
force_multipart=True,
78+
)
79+
80+
return self._model_class.from_response(
81+
response
82+
) # UNUSED type: ignore[attr-defined, no-any-return]
83+
4584
def charges(self, custom_ledger_id: str) -> CustomLedgerChargesService:
4685
"""Return custom ledger charges service."""
4786
return CustomLedgerChargesService(
4887
http_client=self.http_client,
4988
endpoint_params={"custom_ledger_id": custom_ledger_id},
5089
)
5190

52-
def upload(self, custom_ledger_id: str) -> CustomLedgerUploadService:
53-
"""Get the Custom Ledger Upload service."""
54-
return CustomLedgerUploadService(
55-
http_client=self.http_client,
56-
endpoint_params={"custom_ledger_id": custom_ledger_id},
57-
)
58-
5991
def attachments(self, custom_ledger_id: str) -> CustomLedgerAttachmentsService:
6092
"""Return custom ledger attachments service."""
6193
return CustomLedgerAttachmentsService(
@@ -73,20 +105,48 @@ class AsyncCustomLedgersService(
73105
):
74106
"""Async Custom Ledgers service."""
75107

108+
async def upload(self, custom_ledger_id: str, file: FileTypes) -> CustomLedger:
109+
"""Upload custom ledger file.
110+
111+
Args:
112+
custom_ledger_id: Custom Ledger ID.
113+
file: Custom Ledger file.
114+
115+
Returns:
116+
CustomLedger: Created resource.
117+
"""
118+
files: dict[str, FileTypes] = {}
119+
120+
filename = pathlib.Path(getattr(file, "name", "uploaded_file.xlsx")).name
121+
122+
# Mimetype is set to Excel XLSX to prevent 415 response from the server
123+
files[self._upload_file_key] = (
124+
filename,
125+
cast("FileContent", file),
126+
MIMETYPE_EXCEL_XLSX,
127+
) # UNUSED type: ignore[attr-defined]
128+
files[self._upload_data_key] = custom_ledger_id # UNUSED type: ignore
129+
130+
path = urljoin(f"{self.path}/", f"{custom_ledger_id}/upload")
131+
132+
response = await self.http_client.request( # UNUSED type: ignore[attr-defined]
133+
"post",
134+
path, # UNUSED type: ignore[attr-defined]
135+
files=files,
136+
force_multipart=True,
137+
)
138+
139+
return self._model_class.from_response(
140+
response
141+
) # UNUSED type: ignore[attr-defined, no-any-return]
142+
76143
def charges(self, custom_ledger_id: str) -> AsyncCustomLedgerChargesService:
77144
"""Return custom ledger charges service."""
78145
return AsyncCustomLedgerChargesService(
79146
http_client=self.http_client,
80147
endpoint_params={"custom_ledger_id": custom_ledger_id},
81148
)
82149

83-
def upload(self, custom_ledger_id: str) -> AsyncCustomLedgerUploadService:
84-
"""Get the Async Custom Ledger Upload service."""
85-
return AsyncCustomLedgerUploadService(
86-
http_client=self.http_client,
87-
endpoint_params={"custom_ledger_id": custom_ledger_id},
88-
)
89-
90150
def attachments(self, custom_ledger_id: str) -> AsyncCustomLedgerAttachmentsService:
91151
"""Return custom ledger attachments service."""
92152
return AsyncCustomLedgerAttachmentsService(

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,14 @@ per-file-ignores = [
119119
"mpt_api_client/mpt_client.py: WPS214 WPS235",
120120
"mpt_api_client/resources/*: WPS215",
121121
"mpt_api_client/resources/accounts/*.py: WPS202 WPS215 WPS214 WPS235 WPS453",
122-
"mpt_api_client/resources/billing/*.py: WPS202 WPS204 WPS214 WPS215 WPS235",
122+
"mpt_api_client/resources/billing/*.py: WPS202 WPS204 WPS214 WPS215 WPS235 WPS110",
123123
"mpt_api_client/resources/catalog/*.py: WPS110 WPS214 WPS215 WPS235",
124124
"mpt_api_client/resources/catalog/mixins.py: WPS110 WPS202 WPS214 WPS215 WPS235",
125125
"mpt_api_client/resources/catalog/products.py: WPS204 WPS214 WPS215 WPS235",
126126
"mpt_api_client/resources/commerce/*.py: WPS235 WPS215",
127127
"mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214",
128128
"tests/e2e/accounts/*.py: WPS430 WPS202",
129+
"tests/e2e/billing/*.py: WPS202 WPS421 WPS118",
129130
"tests/e2e/catalog/*.py: WPS202 WPS421",
130131
"tests/e2e/catalog/items/*.py: WPS110 WPS202",
131132
"tests/e2e/commerce/*.py: WPS202 WPS204 WPS453",

tests/data/test_custom_ledger.xlsx

14.4 KB
Binary file not shown.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def custom_ledger_attachment_id(e2e_config):
6+
return e2e_config["billing.custom_ledger.attachment.id"]
7+
8+
9+
@pytest.fixture
10+
def invalid_custom_ledger_attachment_id():
11+
return "CLA-0000-0000"
12+
13+
14+
@pytest.fixture
15+
def custom_ledger_attachment_factory():
16+
def factory(
17+
name: str = "E2E Created Custom Ledger Attachment",
18+
):
19+
return {
20+
"name": name,
21+
"description": name,
22+
}
23+
24+
return factory
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import pytest
2+
3+
from mpt_api_client.exceptions import MPTAPIError
4+
from mpt_api_client.rql.query_builder import RQLQuery
5+
6+
pytestmark = [pytest.mark.flaky]
7+
8+
9+
@pytest.fixture
10+
async def created_custom_ledger_attachment(
11+
async_mpt_ops, custom_ledger_attachment_factory, custom_ledger_id, pdf_fd
12+
):
13+
new_custom_ledger_attachment_request_data = custom_ledger_attachment_factory(
14+
name="E2E Created Custom Ledger Attachment",
15+
)
16+
custom_ledger_attachments = async_mpt_ops.billing.custom_ledgers.attachments(custom_ledger_id)
17+
18+
created_attachment = await custom_ledger_attachments.create(
19+
new_custom_ledger_attachment_request_data, file=pdf_fd
20+
)
21+
22+
yield created_attachment
23+
24+
try:
25+
await custom_ledger_attachments.delete(created_attachment.id)
26+
except MPTAPIError as error:
27+
print(f"TEARDOWN - Unable to delete custom ledger attachment: {error.title}") # noqa: WPS421
28+
29+
30+
@pytest.fixture
31+
def custom_ledger_attachments(async_mpt_ops, custom_ledger_id):
32+
return async_mpt_ops.billing.custom_ledgers.attachments(custom_ledger_id)
33+
34+
35+
async def test_get_custom_ledger_attachment_by_id(
36+
custom_ledger_attachments, custom_ledger_attachment_id
37+
):
38+
result = await custom_ledger_attachments.get(custom_ledger_attachment_id)
39+
40+
assert result is not None
41+
42+
43+
async def test_get_custom_ledger_attachment_by_id_not_found(
44+
custom_ledger_attachments, invalid_custom_ledger_attachment_id
45+
):
46+
with pytest.raises(MPTAPIError, match=r"404 Not Found"):
47+
await custom_ledger_attachments.get(invalid_custom_ledger_attachment_id)
48+
49+
50+
async def test_list_custom_ledger_attachments(custom_ledger_attachments):
51+
limit = 10
52+
53+
result = await custom_ledger_attachments.fetch_page(limit=limit)
54+
55+
assert len(result) > 0
56+
57+
58+
async def test_filter_custom_ledger_attachments(
59+
custom_ledger_attachments, custom_ledger_attachment_id
60+
):
61+
select_fields = ["-price"]
62+
filtered_attachments = (
63+
custom_ledger_attachments.filter(RQLQuery(id=custom_ledger_attachment_id))
64+
.filter(RQLQuery(name="test_custom_ledger.xlsx"))
65+
.select(*select_fields)
66+
)
67+
68+
result = [attachment async for attachment in filtered_attachments.iterate()]
69+
70+
assert len(result) == 1
71+
72+
73+
def test_create_billing_custom_ledger_attachment(created_custom_ledger_attachment):
74+
result = created_custom_ledger_attachment
75+
76+
assert result is not None
77+
78+
79+
async def test_update_billing_custom_ledger_attachment(
80+
custom_ledger_attachments, created_custom_ledger_attachment
81+
):
82+
updated_name = "E2E Updated Custom Ledger Attachment"
83+
84+
update_data = {
85+
"name": updated_name,
86+
}
87+
88+
updated_attachment = await custom_ledger_attachments.update(
89+
created_custom_ledger_attachment.id,
90+
update_data,
91+
)
92+
93+
assert updated_attachment.name == updated_name
94+
95+
96+
async def test_delete_billing_custom_ledger_attachment(
97+
custom_ledger_attachments, created_custom_ledger_attachment
98+
):
99+
result = created_custom_ledger_attachment
100+
101+
await custom_ledger_attachments.delete(result.id)
102+
103+
104+
async def test_download_billing_custom_ledger_attachment(
105+
custom_ledger_attachments, created_custom_ledger_attachment
106+
):
107+
result = await custom_ledger_attachments.download(created_custom_ledger_attachment.id)
108+
109+
assert result.file_contents is not None
110+
assert result.filename is not None

0 commit comments

Comments
 (0)