Skip to content

Commit aa89e23

Browse files
authored
[MPT-14910] Add e2e tests for some billing journals endpoints (#172)
2 parents 359d60e + 832937c commit aa89e23

File tree

11 files changed

+392
-112
lines changed

11 files changed

+392
-112
lines changed

e2e_config.test.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"accounts.seller.id": "SEL-7310-3075",
1212
"accounts.user.id": "USR-9673-3314",
1313
"accounts.user_group.id": "UGR-6822-0561",
14+
"billing.journal.id": "BJO-6562-0928",
1415
"catalog.authorization.id": "AUT-9288-6146",
1516
"catalog.listing.id": "LST-5489-0806",
1617
"catalog.price_list.id": "PRC-7255-3950-0245",
@@ -24,9 +25,9 @@
2425
"catalog.product.terms.id": "TCS-7255-3950-0001",
2526
"catalog.product.terms.variant.id": "TCV-7255-3950-0001-0001",
2627
"catalog.unit.id": "UNT-1229",
27-
"commerce.agreement.attachment.id": "ATT-9850-2169-6098-0001",
28-
"commerce.agreement.id": "AGR-9850-2169-6098",
29-
"commerce.agreement.subscription.line.id": "ALI-9850-2169-6098-0001",
28+
"commerce.agreement.attachment.id": "ATT-0078-7880-7436-0001",
29+
"commerce.agreement.id": "AGR-0078-7880-7436",
30+
"commerce.agreement.subscription.line.id": "ALI-0078-7880-7436-0001",
3031
"commerce.assets.agreement.id": "AGR-2473-3299-1721",
3132
"commerce.assets.agreement.line.id": "ALI-9320-4904-7940-0001",
3233
"commerce.assets.id": "AST-0625-6526-6154",
@@ -37,7 +38,7 @@
3738
"commerce.assets.product.template.id": "",
3839
"commerce.authorization.id": "AUT-0031-2873",
3940
"commerce.client.id": "ACC-1086-6867",
40-
"commerce.order.id": "ORD-6969-3541-5426",
41+
"commerce.order.id": "ORD-0557-5037-6263",
4142
"commerce.product.id": "PRD-1767-7355",
4243
"commerce.product.item.id": "ITM-1767-7355-0001",
4344
"commerce.product.listing.id": "LST-5489-0806",

mpt_api_client/resources/billing/journal_upload.py

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

mpt_api_client/resources/billing/journals.py

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
from urllib.parse import urljoin
2+
13
from mpt_api_client.http import AsyncService, Service
24
from mpt_api_client.http.mixins import (
35
AsyncCollectionMixin,
46
AsyncManagedResourceMixin,
57
CollectionMixin,
68
ManagedResourceMixin,
79
)
10+
from mpt_api_client.http.types import FileTypes
811
from mpt_api_client.models import Model
912
from mpt_api_client.resources.billing.journal_attachments import (
1013
AsyncJournalAttachmentsService,
@@ -18,10 +21,6 @@
1821
AsyncJournalSellersService,
1922
JournalSellersService,
2023
)
21-
from mpt_api_client.resources.billing.journal_upload import (
22-
AsyncJournalUploadService,
23-
JournalUploadService,
24-
)
2524
from mpt_api_client.resources.billing.mixins import AsyncRegeneratableMixin, RegeneratableMixin
2625

2726

@@ -35,6 +34,8 @@ class JournalsServiceConfig:
3534
_endpoint = "/public/v1/billing/journals"
3635
_model_class = Journal
3736
_collection_key = "data"
37+
_upload_file_key = "file"
38+
_upload_data_key = "id"
3839

3940

4041
class JournalsService(
@@ -46,6 +47,35 @@ class JournalsService(
4647
):
4748
"""Journals service."""
4849

50+
def upload(self, journal_id: str, file: FileTypes | None = None) -> Journal: # noqa: WPS110
51+
"""Upload journal file.
52+
53+
Args:
54+
journal_id: Journal ID.
55+
file: journal file.
56+
57+
Returns:
58+
Journal: Created resource.
59+
"""
60+
files = {}
61+
62+
if file:
63+
files[self._upload_file_key] = file # UNUSED type: ignore[attr-defined]
64+
files[self._upload_data_key] = journal_id # UNUSED type: ignore
65+
66+
path = urljoin(f"{self.path}/", f"{journal_id}/upload")
67+
68+
response = self.http_client.request( # UNUSED type: ignore[attr-defined]
69+
"post",
70+
path, # UNUSED type: ignore[attr-defined]
71+
files=files,
72+
force_multipart=True,
73+
)
74+
75+
return self._model_class.from_response(
76+
response
77+
) # UNUSED type: ignore[attr-defined, no-any-return]
78+
4979
def attachments(self, journal_id: str) -> JournalAttachmentsService:
5080
"""Return journal attachments service."""
5181
return JournalAttachmentsService(
@@ -65,12 +95,6 @@ def charges(self, journal_id: str) -> JournalChargesService:
6595
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
6696
)
6797

68-
def upload(self, journal_id: str) -> JournalUploadService:
69-
"""Return journal upload service."""
70-
return JournalUploadService(
71-
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
72-
)
73-
7498

7599
class AsyncJournalsService(
76100
AsyncRegeneratableMixin[Journal],
@@ -81,6 +105,35 @@ class AsyncJournalsService(
81105
):
82106
"""Async Journals service."""
83107

108+
async def upload(self, journal_id: str, file: FileTypes | None = None) -> Journal: # noqa: WPS110
109+
"""Upload journal file.
110+
111+
Args:
112+
journal_id: Journal ID.
113+
file: journal file.
114+
115+
Returns:
116+
Journal: Created resource.
117+
"""
118+
files = {}
119+
120+
if file:
121+
files[self._upload_file_key] = file # UNUSED type: ignore[attr-defined]
122+
files[self._upload_data_key] = journal_id # UNUSED type: ignore
123+
124+
path = urljoin(f"{self.path}/", f"{journal_id}/upload")
125+
126+
response = await self.http_client.request( # UNUSED type: ignore[attr-defined]
127+
"post",
128+
path, # UNUSED type: ignore[attr-defined]
129+
files=files,
130+
force_multipart=True,
131+
)
132+
133+
return self._model_class.from_response(
134+
response
135+
) # UNUSED type: ignore[attr-defined, no-any-return]
136+
84137
def attachments(self, journal_id: str) -> AsyncJournalAttachmentsService:
85138
"""Return journal attachments service."""
86139
return AsyncJournalAttachmentsService(
@@ -99,9 +152,3 @@ def charges(self, journal_id: str) -> AsyncJournalChargesService:
99152
return AsyncJournalChargesService(
100153
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
101154
)
102-
103-
def upload(self, journal_id: str) -> AsyncJournalUploadService:
104-
"""Return journal upload service."""
105-
return AsyncJournalUploadService(
106-
http_client=self.http_client, endpoint_params={"journal_id": journal_id}
107-
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"externalIds": {"vendor": "ext-seeded-billing-sub-vendor-id", "invoice": "INV12345", "reference": "ORD-7924-7691-0805"}, "search": {"source": {"type": "Subscription", "criteria": "id", "value": "SUB-5839-4140-9574"},"order":{"criteria":"order.id","value":"ORD-7924-7691-0805"}, "item": {"criteria": "item.id", "value": "ITM-1767-7355-0001"}}, "period": {"start": "2025-12-22", "end": "2026-12-21"}, "price": {"unitPP": 10, "PPx1": 8.33}, "quantity": 10, "segment": "COM", "description": {"value1": "desc-1", "value2": "desc-2"}}
9.18 KB
Binary file not shown.

tests/e2e/billing/conftest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import pathlib
2+
3+
import pytest
4+
5+
6+
@pytest.fixture
7+
def billing_journal_fd():
8+
file_path = pathlib.Path("tests/data/test_billing_journal.jsonl").resolve()
9+
fd = file_path.open("rb")
10+
try:
11+
yield fd
12+
finally:
13+
fd.close()
14+
15+
16+
@pytest.fixture
17+
def billing_journal_id(e2e_config):
18+
return e2e_config["billing.journal.id"]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def invalid_billing_journal_id():
6+
return "BJO-0000-0000"
7+
8+
9+
@pytest.fixture
10+
def billing_journal_factory(authorization_id):
11+
def factory(
12+
name: str = "E2E Created Billing Journal",
13+
):
14+
return {
15+
"authorization": {"id": authorization_id},
16+
"dueDate": "2026-01-02T19:00:00.000Z",
17+
"externalIds": {},
18+
"name": name,
19+
}
20+
21+
return factory
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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_billing_journal(async_mpt_vendor, billing_journal_factory):
11+
new_billing_journal_request_data = billing_journal_factory(
12+
name="E2E Created Billing Journal",
13+
)
14+
15+
created_billing_journal = await async_mpt_vendor.billing.journals.create(
16+
new_billing_journal_request_data
17+
)
18+
19+
yield created_billing_journal
20+
21+
try:
22+
await async_mpt_vendor.billing.journals.delete(created_billing_journal.id)
23+
except MPTAPIError as error:
24+
print(f"TEARDOWN - Unable to delete billing journal: {error.title}") # noqa: WPS421
25+
26+
27+
@pytest.fixture
28+
async def submitted_billing_journal(async_mpt_vendor, created_billing_journal, billing_journal_fd):
29+
await async_mpt_vendor.billing.journals.submit(created_billing_journal.id)
30+
await async_mpt_vendor.billing.journals.upload(
31+
journal_id=created_billing_journal.id,
32+
file=billing_journal_fd,
33+
)
34+
35+
return created_billing_journal
36+
37+
38+
@pytest.fixture
39+
async def completed_billing_journal(async_mpt_vendor, submitted_billing_journal):
40+
await async_mpt_vendor.billing.journals.accept(submitted_billing_journal.id)
41+
await async_mpt_vendor.billing.journals.complete(submitted_billing_journal.id)
42+
return submitted_billing_journal
43+
44+
45+
async def test_get_billing_journal_by_id(async_mpt_vendor, billing_journal_id):
46+
result = await async_mpt_vendor.billing.journals.get(billing_journal_id)
47+
48+
assert result is not None
49+
50+
51+
async def test_list_billing_journals(async_mpt_vendor):
52+
limit = 10
53+
54+
result = await async_mpt_vendor.billing.journals.fetch_page(limit=limit)
55+
56+
assert len(result) > 0
57+
58+
59+
async def test_get_billing_journal_by_id_not_found(async_mpt_vendor, invalid_billing_journal_id):
60+
with pytest.raises(MPTAPIError, match=r"404 Not Found"):
61+
await async_mpt_vendor.billing.journals.get(invalid_billing_journal_id)
62+
63+
64+
async def test_filter_billing_journals(async_mpt_vendor, billing_journal_id):
65+
select_fields = ["-value"]
66+
filtered_billing_journals = (
67+
async_mpt_vendor.billing.journals.filter(RQLQuery(id=billing_journal_id))
68+
.filter(RQLQuery(name="E2E Seeded Billing Journal"))
69+
.select(*select_fields)
70+
)
71+
72+
result = [billing_journal async for billing_journal in filtered_billing_journals.iterate()]
73+
74+
assert len(result) == 1
75+
76+
77+
def test_create_billing_journal(created_billing_journal):
78+
result = created_billing_journal
79+
80+
assert result is not None
81+
82+
83+
async def test_update_billing_journal(
84+
async_mpt_vendor, created_billing_journal, billing_journal_factory
85+
):
86+
updated_name = "E2E Updated Billing Journal Name"
87+
updated_billing_journal_data = billing_journal_factory(name=updated_name)
88+
89+
result = await async_mpt_vendor.billing.journals.update(
90+
created_billing_journal.id,
91+
updated_billing_journal_data,
92+
)
93+
94+
assert result.name == updated_name
95+
96+
97+
async def test_delete_billing_journal(async_mpt_vendor, created_billing_journal):
98+
result = created_billing_journal
99+
100+
await async_mpt_vendor.billing.journals.delete(result.id)
101+
102+
103+
async def test_upload_billing_journal(
104+
async_mpt_vendor, created_billing_journal, billing_journal_fd
105+
):
106+
result = await async_mpt_vendor.billing.journals.upload(
107+
journal_id=created_billing_journal.id,
108+
file=billing_journal_fd,
109+
)
110+
111+
assert result is not None

0 commit comments

Comments
 (0)