Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions e2e_config.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@
"commerce.product.listing.id": "LST-5489-0806",
"commerce.product.template.id": "TPL-1767-7355-0002",
"commerce.user.id": "USR-4303-2348",
"commerce.subscription.agreement.id": "AGR-2473-3299-1721",
"commerce.subscription.id": "SUB-3678-1831-2188",
"commerce.subscription.product.item.id": "ITM-1767-7355-0001",
"notifications.message.id": "MSG-0000-6215-1019-0139"
}
33 changes: 33 additions & 0 deletions mpt_api_client/resources/commerce/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from mpt_api_client.models.model import ResourceData


class TerminateMixin[Model]:
"""Terminate resource mixin."""

def terminate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
"""Terminate resource.

Args:
resource_id: Resource ID
resource_data: Resource data

Returns:
Terminated resource.
"""
return self._resource_action(resource_id, "POST", "terminate", json=resource_data) # type: ignore[attr-defined, no-any-return]


class AsyncTerminateMixin[Model]:
"""Asynchronous terminate resource mixin."""

async def terminate(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
"""Terminate resource.

Args:
resource_id: Resource ID
resource_data: Resource data

Returns:
Terminated resource.
"""
return await self._resource_action(resource_id, "POST", "terminate", json=resource_data) # type: ignore[attr-defined, no-any-return]
37 changes: 8 additions & 29 deletions mpt_api_client/resources/commerce/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncCreateMixin,
AsyncDeleteMixin,
AsyncGetMixin,
AsyncUpdateMixin,
CollectionMixin,
CreateMixin,
DeleteMixin,
GetMixin,
UpdateMixin,
)
from mpt_api_client.models import Model, ResourceData
from mpt_api_client.models import Model
from mpt_api_client.resources.commerce.mixins import AsyncTerminateMixin, TerminateMixin


class Subscription(Model):
Expand All @@ -29,9 +30,10 @@ class SubscriptionsServiceConfig:

class SubscriptionsService( # noqa: WPS215
CreateMixin[Subscription],
DeleteMixin,
UpdateMixin[Subscription],
GetMixin[Subscription],
CollectionMixin[Subscription],
TerminateMixin[Subscription],
Service[Subscription],
SubscriptionsServiceConfig,
):
Expand All @@ -49,24 +51,13 @@ def render(self, resource_id: str) -> str:
response = self._resource_do_request(resource_id, "GET", "render")
return response.text

def terminate(self, resource_id: str, resource_data: ResourceData) -> Subscription:
"""Terminate subscription.

Args:
resource_id: Order resource ID
resource_data: Order resource data

Returns:
Subscription template text in markdown format.
"""
return self._resource_action(resource_id, "POST", "terminate", json=resource_data)


class AsyncSubscriptionsService( # noqa: WPS215
AsyncCreateMixin[Subscription],
AsyncDeleteMixin,
AsyncUpdateMixin[Subscription],
AsyncGetMixin[Subscription],
AsyncCollectionMixin[Subscription],
AsyncTerminateMixin[Subscription],
AsyncService[Subscription],
SubscriptionsServiceConfig,
):
Expand All @@ -83,15 +74,3 @@ async def render(self, resource_id: str) -> str:
"""
response = await self._resource_do_request(resource_id, "GET", "render")
return response.text

async def terminate(self, resource_id: str, resource_data: ResourceData) -> Subscription:
"""Terminate subscription.

Args:
resource_id: Order resource ID
resource_data: Order resource data

Returns:
Subscription template text in markdown format.
"""
return await self._resource_action(resource_id, "POST", "terminate", json=resource_data)
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ filterwarnings = [
"ignore:pkg_resources is deprecated as an API:DeprecationWarning",
]
rp_project = "mpt-api-python-client"
markers = [
"flaky: mark test as flaky (may fail intermittently)",
]

[tool.coverage.run]
branch = true
Expand Down Expand Up @@ -120,14 +123,16 @@ per-file-ignores = [
"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/catalog/*.py: WPS202 WPS421",
"tests/e2e/catalog/items/*.py: WPS110 WPS202",
"tests/e2e/commerce/*.py: WPS204 WPS453",
"tests/e2e/commerce/*.py: WPS202 WPS204 WPS453",
"tests/e2e/commerce/agreement/*.py: WPS202",
"tests/e2e/commerce/agreement/attachment/*.py: WPS202",
"tests/e2e/commerce/order/*.py: WPS202 WPS204",
"tests/e2e/commerce/subscription/*.py: WPS202",
"tests/unit/http/test_async_service.py: WPS204 WPS202",
"tests/unit/http/test_service.py: WPS204 WPS202",
"tests/unit/http/test_mixins.py: WPS204 WPS202 WPS210",
Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/commerce/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,13 @@ def commerce_product_template_id(e2e_config):
@pytest.fixture
def commerce_user_id(e2e_config):
return e2e_config["commerce.user.id"]


@pytest.fixture
def subscription_item_id(e2e_config):
return e2e_config["commerce.subscription.product.item.id"]


@pytest.fixture
def subscription_agreement_id(e2e_config):
return e2e_config["commerce.subscription.agreement.id"]
41 changes: 41 additions & 0 deletions tests/e2e/commerce/subscription/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest
from freezegun import freeze_time


@pytest.fixture
def subscription_id(e2e_config):
return e2e_config["commerce.subscription.id"]


@pytest.fixture
def invalid_subscription_id():
return "SUB-0000-0000-0000"


@pytest.fixture
def subscription_factory(subscription_agreement_id, subscription_item_id):
@freeze_time("2025-11-14T09:00:00.000Z")
def factory(
name: str = "E2E Created Subscription",
external_vendor_id: str = "ext-vendor-id",
quantity: int = 1,
):
return {
"name": name,
"startDate": "2025-11-03T09:00:00.000Z",
"commitmentDate": "2026-11-02T09:00:00.000Z",
"autoRenew": True,
"agreement": {"id": subscription_agreement_id},
"externalIds": {"vendor": external_vendor_id},
"template": None,
"lines": [
{
"item": {"id": subscription_item_id},
"quantity": quantity,
"price": {"unitPP": 10},
}
],
"parameters": {"fulfillment": []},
}

return factory
82 changes: 82 additions & 0 deletions tests/e2e/commerce/subscription/test_async_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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_subscription(async_mpt_vendor, subscription_factory):
subscription_data = subscription_factory()

subscription = await async_mpt_vendor.commerce.subscriptions.create(subscription_data)

yield subscription

try:
await async_mpt_vendor.commerce.subscriptions.terminate(subscription.id)
except MPTAPIError as error:
print(f"TEARDOWN - Unable to terminate subscription: {getattr(error, 'title', str(error))}") # noqa: WPS421


async def test_get_subscription_by_id(async_mpt_vendor, subscription_id):
result = await async_mpt_vendor.commerce.subscriptions.get(subscription_id)

assert result is not None


async def test_list_subscriptions(async_mpt_vendor):
limit = 10

result = await async_mpt_vendor.commerce.subscriptions.fetch_page(limit=limit)

assert result is not None


async def test_get_subscription_by_id_not_found(async_mpt_vendor, invalid_subscription_id):
with pytest.raises(MPTAPIError, match="404 Not Found"):
await async_mpt_vendor.commerce.subscriptions.get(invalid_subscription_id)


async def test_filter_subscriptions(async_mpt_vendor, subscription_id):
select_fields = ["-externalIds"]
filtered_subscriptions = (
async_mpt_vendor.commerce.subscriptions.filter(RQLQuery(id=subscription_id))
.filter(RQLQuery(name="E2E Seeded Subscription"))
.select(*select_fields)
)

result = [subscription async for subscription in filtered_subscriptions.iterate()]

assert len(result) == 1


def test_create_subscription(created_subscription):
result = created_subscription

assert result is not None


async def test_update_subscription(async_mpt_vendor, created_subscription):
updated_subscription_data = {
"name": "E2E Updated Subscription",
}

result = await async_mpt_vendor.commerce.subscriptions.update(
created_subscription.id, updated_subscription_data
)

assert result is not None


async def test_terminate_subscription(async_mpt_vendor, created_subscription):
result = await async_mpt_vendor.commerce.subscriptions.terminate(created_subscription.id)

assert result is not None


async def test_render_subscription(async_mpt_vendor, created_subscription):
result = await async_mpt_vendor.commerce.subscriptions.render(created_subscription.id)

assert result is not None
82 changes: 82 additions & 0 deletions tests/e2e/commerce/subscription/test_sync_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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_subscription(mpt_vendor, subscription_factory):
subscription_data = subscription_factory()

subscription = mpt_vendor.commerce.subscriptions.create(subscription_data)

yield subscription

try:
mpt_vendor.commerce.subscriptions.terminate(subscription.id)
except MPTAPIError as error:
print(f"TEARDOWN - Unable to terminate subscription: {getattr(error, 'title', str(error))}") # noqa: WPS421


def test_get_subscription_by_id(mpt_vendor, subscription_id):
result = mpt_vendor.commerce.subscriptions.get(subscription_id)

assert result is not None


def test_list_subscriptions(mpt_vendor):
limit = 10

result = mpt_vendor.commerce.subscriptions.fetch_page(limit=limit)

assert result is not None


def test_get_subscription_by_id_not_found(mpt_vendor, invalid_subscription_id):
with pytest.raises(MPTAPIError, match="404 Not Found"):
mpt_vendor.commerce.subscriptions.get(invalid_subscription_id)


def test_filter_subscriptions(mpt_vendor, subscription_id):
select_fields = ["-externalIds"]
filtered_subscriptions = (
mpt_vendor.commerce.subscriptions.filter(RQLQuery(id=subscription_id))
.filter(RQLQuery(name="E2E Seeded Subscription"))
.select(*select_fields)
)

result = list(filtered_subscriptions.iterate())

assert len(result) == 1


def test_create_subscription(created_subscription):
result = created_subscription

assert result is not None


def test_update_subscription(mpt_vendor, created_subscription):
updated_subscription_data = {
"name": "E2E Updated Subscription",
}

result = mpt_vendor.commerce.subscriptions.update(
created_subscription.id, updated_subscription_data
)

assert result is not None


def test_terminate_subscription(mpt_vendor, created_subscription):
result = mpt_vendor.commerce.subscriptions.terminate(created_subscription.id)

assert result is not None


def test_render_subscription(mpt_vendor, created_subscription):
result = mpt_vendor.commerce.subscriptions.render(created_subscription.id)

assert result is not None
Loading