Skip to content
Open
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
1 change: 1 addition & 0 deletions e2e_config.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"notifications.message.id": "MSG-0000-6215-1019-0139",
"notifications.subscriber.id": "NTS-0829-7123-7123",
"integration.extension.id": "EXT-6587-4477",
"integration.installation.id": "EXI-0022-3978-5547",
"integration.term.id": "ETC-6587-4477-0062",
"program.certificate.id": "CER-9646-2171-8417",
"program.document.file.id": "PDM-9643-3741-0001",
Expand Down
69 changes: 69 additions & 0 deletions mpt_api_client/resources/integration/installations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncManagedResourceMixin,
CollectionMixin,
ManagedResourceMixin,
)
from mpt_api_client.models import Model
from mpt_api_client.models.model import BaseModel
from mpt_api_client.resources.integration.mixins import (
AsyncInstallationMixin,
InstallationMixin,
)


class Installation(Model):
"""Installation resource.

Attributes:
name: Installation name.
revision: Revision number.
account: Reference to the account.
extension: Reference to the extension.
status: Installation status (Invited, Installed, Uninstalled, Expired).
configuration: Installation configuration data.
invitation: Invitation details.
modules: Modules included in the installation.
terms: Accepted terms for this installation.
audit: Audit information (created, updated, invited, installed, expired, uninstalled).
"""

name: str | None
revision: int | None
account: BaseModel | None
extension: BaseModel | None
status: str | None
configuration: BaseModel | None
invitation: BaseModel | None
modules: list[BaseModel] | None
terms: list[BaseModel] | None
audit: BaseModel | None


class InstallationsServiceConfig:
"""Installations service configuration."""

_endpoint = "/public/v1/integration/installations"
_model_class = Installation
_collection_key = "data"


class InstallationsService(
InstallationMixin[Installation],
ManagedResourceMixin[Installation],
CollectionMixin[Installation],
Service[Installation],
InstallationsServiceConfig,
):
"""Sync service for the /public/v1/integration/installations endpoint."""


class AsyncInstallationsService(
AsyncInstallationMixin[Installation],
AsyncManagedResourceMixin[Installation],
AsyncCollectionMixin[Installation],
AsyncService[Installation],
InstallationsServiceConfig,
):
"""Async service for the /public/v1/integration/installations endpoint."""
14 changes: 14 additions & 0 deletions mpt_api_client/resources/integration/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
AsyncExtensionsService,
ExtensionsService,
)
from mpt_api_client.resources.integration.installations import (
AsyncInstallationsService,
InstallationsService,
)


class Integration:
Expand All @@ -25,6 +29,11 @@ def categories(self) -> CategoriesService:
"""Categories service."""
return CategoriesService(http_client=self.http_client)

@property
def installations(self) -> InstallationsService:
"""Installations service."""
return InstallationsService(http_client=self.http_client)


class AsyncIntegration:
"""Async Integration MPT API Module."""
Expand All @@ -41,3 +50,8 @@ def extensions(self) -> AsyncExtensionsService:
def categories(self) -> AsyncCategoriesService:
"""Categories service."""
return AsyncCategoriesService(http_client=self.http_client)

@property
def installations(self) -> AsyncInstallationsService:
"""Installations service."""
return AsyncInstallationsService(http_client=self.http_client)
6 changes: 6 additions & 0 deletions mpt_api_client/resources/integration/mixins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
AsyncExtensionMixin,
ExtensionMixin,
)
from mpt_api_client.resources.integration.mixins.installation_mixin import (
AsyncInstallationMixin,
InstallationMixin,
)
from mpt_api_client.resources.integration.mixins.media_mixin import (
AsyncMediaMixin,
MediaMixin,
)

__all__ = [ # noqa: WPS410
"AsyncExtensionMixin",
"AsyncInstallationMixin",
"AsyncMediaMixin",
"ExtensionMixin",
"InstallationMixin",
"MediaMixin",
]
103 changes: 103 additions & 0 deletions mpt_api_client/resources/integration/mixins/installation_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from mpt_api_client.models import ResourceData


class InstallationMixin[Model]:
"""Mixin that adds installation actions: redeem, renew, and token retrieval."""

def redeem(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
"""Redeem an installation invitation.

Args:
resource_id: Installation ID.
resource_data: Redeem payload, for example ``{"code": "...", "modules": [...]}``.

Returns:
Updated installation.
"""
return self._resource(resource_id).post("redeem", json=resource_data) # type: ignore[attr-defined, no-any-return]

def renew(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
"""Renew an installation.

Args:
resource_id: Installation ID.
resource_data: Optional request body.

Returns:
Updated installation.
"""
return self._resource(resource_id).post("renew", json=resource_data) # type: ignore[attr-defined, no-any-return]

def token(self, resource_id: str) -> Model:
"""Retrieve an access token for a specific installation.

Args:
resource_id: Installation ID.

Returns:
Token response.
"""
return self._resource(resource_id).post("token") # type: ignore[attr-defined, no-any-return]

def token_for_account(self, account_id: str | None = None) -> Model:
"""Retrieve an installation token for an account.

Args:
account_id: Optional account ID sent as ``account.id`` query parameter.

Returns:
Token response.
"""
query_params = {"account.id": account_id} if account_id else None
return self._resource("-").post("token", query_params=query_params) # type: ignore[attr-defined, no-any-return]


class AsyncInstallationMixin[Model]:
"""Async mixin for installation actions: redeem, renew, and token retrieval."""

async def redeem(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
"""Redeem an installation invitation.

Args:
resource_id: Installation ID.
resource_data: Redeem payload, for example ``{"code": "...", "modules": [...]}``.

Returns:
Updated installation.
"""
return await self._resource(resource_id).post("redeem", json=resource_data) # type: ignore[attr-defined, no-any-return]

async def renew(self, resource_id: str, resource_data: ResourceData | None = None) -> Model:
"""Renew an installation.

Args:
resource_id: Installation ID.
resource_data: Optional request body.

Returns:
Updated installation.
"""
return await self._resource(resource_id).post("renew", json=resource_data) # type: ignore[attr-defined, no-any-return]

async def token(self, resource_id: str) -> Model:
"""Retrieve an access token for a specific installation.

Args:
resource_id: Installation ID.

Returns:
Token response.
"""
return await self._resource(resource_id).post("token") # type: ignore[attr-defined, no-any-return]

async def token_for_account(self, account_id: str | None = None) -> Model:
"""Retrieve an installation token for an account.

Args:
account_id: Optional account ID sent as ``account.id`` query parameter.

Returns:
Token response.
"""
query_params = {"account.id": account_id} if account_id else None
return await self._resource("-").post("token", query_params=query_params) # type: ignore[attr-defined, no-any-return]
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ per-file-ignores = [
"tests/unit/resources/catalog/test_products.py: WPS202 WPS210",
"tests/e2e/integration/*.py: WPS453",
"tests/e2e/integration/extensions/*.py: WPS453 WPS202",
"tests/e2e/integration/installations/*.py: WPS453 WPS202",
"tests/unit/resources/integration/*.py: WPS202 WPS210 WPS218 WPS453",
"tests/unit/resources/integration/mixins/*.py: WPS453 WPS202",
"tests/unit/resources/commerce/*.py: WPS202 WPS204",
Expand Down
6 changes: 6 additions & 0 deletions tests/e2e/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import pytest


@pytest.fixture
def extension_id(e2e_config):
return e2e_config["integration.extension.id"]
13 changes: 4 additions & 9 deletions tests/e2e/integration/extensions/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@


@pytest.fixture
def extensions_service(mpt_ops):
return mpt_ops.integration.extensions
def extensions_service(mpt_vendor):
return mpt_vendor.integration.extensions


@pytest.fixture
def async_extensions_service(async_mpt_ops):
return async_mpt_ops.integration.extensions


@pytest.fixture(scope="session")
def extension_id(e2e_config):
return e2e_config["integration.extension.id"]
def async_extensions_service(async_mpt_vendor):
return async_mpt_vendor.integration.extensions


@pytest.fixture
Expand Down
4 changes: 0 additions & 4 deletions tests/e2e/integration/extensions/test_async_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
pytestmark = [pytest.mark.flaky]


@pytest.mark.skip(reason="unable to create extensions for testing")
def test_create_extension(async_created_extension, extension_data):
Comment thread
coderabbitai[bot] marked this conversation as resolved.
result = async_created_extension.name

Expand All @@ -27,7 +26,6 @@ async def test_get_extension_not_found(async_extensions_service):
await async_extensions_service.get(bogus_id)


@pytest.mark.skip(reason="unable to create extensions for testing")
async def test_update_extension(
async_extensions_service, async_created_extension, logo_fd, short_uuid
):
Expand All @@ -40,7 +38,6 @@ async def test_update_extension(
assert result.name == update_data["name"]


@pytest.mark.skip(reason="unable to create extensions for testing")
async def test_delete_extension(async_extensions_service, async_created_extension):
await async_extensions_service.delete(async_created_extension.id) # act

Expand All @@ -51,7 +48,6 @@ async def test_filter_extensions(async_extensions_service, extension_id):
) # act


@pytest.mark.skip(reason="unable to create extensions for testing")
async def test_download_icon(async_extensions_service, async_created_extension):
result = await async_extensions_service.download_icon(async_created_extension.id)

Expand Down
3 changes: 0 additions & 3 deletions tests/e2e/integration/extensions/test_sync_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
]


@pytest.mark.skip(reason="unable to create extensions for testing")
def test_create_extension(created_extension, extension_data):
result = created_extension.name

Expand All @@ -29,7 +28,6 @@ def test_get_extension_not_found(extensions_service):
extensions_service.get(bogus_id)


@pytest.mark.skip(reason="unable to create extensions for testing")
def test_update_extension(extensions_service, created_extension, logo_fd, short_uuid):
update_data = {"name": f"e2e - please delete {short_uuid}"}

Expand All @@ -38,7 +36,6 @@ def test_update_extension(extensions_service, created_extension, logo_fd, short_
assert result.name == update_data["name"]


@pytest.mark.skip(reason="unable to create extensions for testing")
def test_delete_extension(extensions_service, created_extension):
extensions_service.delete(created_extension.id) # act

Expand Down
Empty file.
Loading