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
57 changes: 57 additions & 0 deletions mpt_api_client/resources/integration/categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
AsyncCreateMixin,
AsyncModifiableResourceMixin,
CollectionMixin,
CreateMixin,
ModifiableResourceMixin,
)
from mpt_api_client.models import Model
from mpt_api_client.models.model import BaseModel


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

Attributes:
name: Category name.
revision: Revision number.
description: Category description.
status: Category status (Active or Disabled).
audit: Audit information (created, updated events).
"""

name: str | None
revision: int | None
description: str | None
status: str | None
audit: BaseModel | None


class CategoriesServiceConfig:
"""Categories service configuration."""

_endpoint = "/public/v1/integration/categories"
_model_class = Category
_collection_key = "data"


class CategoriesService(
CreateMixin[Category],
ModifiableResourceMixin[Category],
CollectionMixin[Category],
Service[Category],
CategoriesServiceConfig,
):
"""Sync service for the /public/v1/integration/categories endpoint."""


class AsyncCategoriesService(
AsyncCreateMixin[Category],
AsyncModifiableResourceMixin[Category],
AsyncCollectionMixin[Category],
AsyncService[Category],
CategoriesServiceConfig,
):
"""Async service for the /public/v1/integration/categories endpoint."""
14 changes: 14 additions & 0 deletions mpt_api_client/resources/integration/integration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from mpt_api_client.http import AsyncHTTPClient, HTTPClient
from mpt_api_client.resources.integration.categories import (
AsyncCategoriesService,
CategoriesService,
)
from mpt_api_client.resources.integration.extensions import (
AsyncExtensionsService,
ExtensionsService,
Expand All @@ -16,6 +20,11 @@ def extensions(self) -> ExtensionsService:
"""Extensions service."""
return ExtensionsService(http_client=self.http_client)

@property
def categories(self) -> CategoriesService:
"""Categories service."""
return CategoriesService(http_client=self.http_client)


class AsyncIntegration:
"""Async Integration MPT API Module."""
Expand All @@ -27,3 +36,8 @@ def __init__(self, *, http_client: AsyncHTTPClient):
def extensions(self) -> AsyncExtensionsService:
"""Extensions service."""
return AsyncExtensionsService(http_client=self.http_client)

@property
def categories(self) -> AsyncCategoriesService:
"""Categories service."""
return AsyncCategoriesService(http_client=self.http_client)
Empty file.
48 changes: 48 additions & 0 deletions tests/e2e/integration/categories/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import pytest

from tests.e2e.helper import (
async_create_fixture_resource_and_delete,
create_fixture_resource_and_delete,
)


@pytest.fixture
def categories_service(mpt_ops):
return mpt_ops.integration.categories


@pytest.fixture
def async_categories_service(async_mpt_ops):
return async_mpt_ops.integration.categories


@pytest.fixture
def category_data(short_uuid):
return {
"name": f"e2e - please delete {short_uuid}",
"description": "Created by automated E2E tests. Safe to delete.",
}


@pytest.fixture
def created_category(categories_service, category_data):
with create_fixture_resource_and_delete(categories_service, category_data) as category:
yield category


@pytest.fixture
async def async_created_category(async_categories_service, category_data):
async with async_create_fixture_resource_and_delete(
async_categories_service, category_data
) as category:
yield category


@pytest.fixture
def category_id(created_category):
return created_category.id


@pytest.fixture
def async_category_id(async_created_category):
return async_created_category.id
36 changes: 36 additions & 0 deletions tests/e2e/integration/categories/test_async_categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest

from tests.e2e.helper import (
assert_async_service_filter_with_iterate,
assert_async_update_resource,
)

pytestmark = [
pytest.mark.flaky,
]


def test_async_create_category(async_created_category, category_data):
result = async_created_category

assert result.name == category_data["name"]


async def test_async_filter_categories(async_categories_service, async_category_id):
await assert_async_service_filter_with_iterate(
async_categories_service, async_category_id, None
) # act


async def test_async_update_category(async_categories_service, async_created_category, short_uuid):
await assert_async_update_resource(
async_categories_service,
async_created_category.id,
"name",
f"e2e updated {short_uuid}",
)


@pytest.mark.skip(reason="categories endpoint does not support delete in E2E environment")
async def test_async_delete_category(async_categories_service, async_created_category):
await async_categories_service.delete(async_created_category.id) # act
31 changes: 31 additions & 0 deletions tests/e2e/integration/categories/test_sync_categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pytest

from tests.e2e.helper import assert_service_filter_with_iterate, assert_update_resource

pytestmark = [
pytest.mark.flaky,
]


def test_create_category(created_category, category_data):
result = created_category

assert result.name == category_data["name"]


def test_filter_categories(categories_service, category_id):
assert_service_filter_with_iterate(categories_service, category_id, None) # act


def test_update_category(categories_service, created_category, short_uuid):
assert_update_resource(
categories_service,
created_category.id,
"name",
f"e2e updated {short_uuid}",
) # act


@pytest.mark.skip(reason="categories endpoint does not support delete in E2E environment")
def test_delete_category(categories_service, created_category):
categories_service.delete(created_category.id) # act
138 changes: 138 additions & 0 deletions tests/unit/resources/integration/test_categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import httpx
import pytest
import respx

from mpt_api_client.models.model import BaseModel
from mpt_api_client.resources.integration.categories import (
AsyncCategoriesService,
CategoriesService,
Category,
)


@pytest.fixture
def categories_service(http_client):
return CategoriesService(http_client=http_client)


@pytest.fixture
def async_categories_service(async_http_client):
return AsyncCategoriesService(http_client=async_http_client)


@pytest.mark.parametrize(
"method",
[
"get",
"create",
"update",
"delete",
"iterate",
],
)
def test_mixins_present(categories_service, method):
result = hasattr(categories_service, method)

assert result is True


@pytest.mark.parametrize(
"method",
[
"get",
"create",
"update",
"delete",
"iterate",
],
)
def test_async_mixins_present(async_categories_service, method):
result = hasattr(async_categories_service, method)

assert result is True


def test_categories_service_initialization(http_client):
result = CategoriesService(http_client=http_client)

assert result.http_client is http_client
assert isinstance(result, CategoriesService)


def test_async_categories_service_initialization(async_http_client):
result = AsyncCategoriesService(http_client=async_http_client)

assert result.http_client is async_http_client
assert isinstance(result, AsyncCategoriesService)


@pytest.fixture
def category_data():
return {
"id": "CAT-001",
"name": "My Category",
"revision": 2,
"description": "A test category",
"status": "Active",
"audit": {"created": {"at": "2024-01-01T00:00:00Z"}},
}


def test_category_primitive_fields(category_data):
result = Category(category_data)

assert result.id == "CAT-001"
assert result.name == "My Category"
assert result.revision == 2
assert result.description == "A test category"
assert result.status == "Active"


def test_category_audit_is_base_model(category_data):
result = Category(category_data)

assert isinstance(result.audit, BaseModel)


def test_category_optional_fields_absent():
result = Category({"id": "CAT-001"})

assert result.id == "CAT-001"
assert not hasattr(result, "name")
assert not hasattr(result, "status")
assert not hasattr(result, "audit")


def test_category_create(categories_service):
payload = {"name": "New Category", "description": "Created via API"}
expected_response = {"id": "CAT-002", "name": "New Category"}
with respx.mock:
mock_route = respx.post("https://api.example.com/public/v1/integration/categories").mock(
return_value=httpx.Response(httpx.codes.CREATED, json=expected_response)
)

result = categories_service.create(payload) # act

assert mock_route.call_count == 1
assert mock_route.calls[0].request.method == "POST"
assert result.to_dict() == expected_response


def test_category_list(categories_service):
response_data = {
"data": [
{"id": "CAT-001", "name": "Category One"},
{"id": "CAT-002", "name": "Category Two"},
]
}
with respx.mock:
mock_route = respx.get("https://api.example.com/public/v1/integration/categories").mock(
return_value=httpx.Response(httpx.codes.OK, json=response_data)
)

result = list(categories_service.iterate()) # act

assert mock_route.call_count == 1
assert len(result) == 2
assert result[0].id == "CAT-001"
assert result[1].id == "CAT-002"
6 changes: 6 additions & 0 deletions tests/unit/resources/integration/test_integration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import pytest

from mpt_api_client.resources.integration.categories import (
AsyncCategoriesService,
CategoriesService,
)
from mpt_api_client.resources.integration.extensions import (
AsyncExtensionsService,
ExtensionsService,
Expand Down Expand Up @@ -38,6 +42,7 @@ def test_async_integration_initialization(async_http_client):
("property_name", "expected_service_class"),
[
("extensions", ExtensionsService),
("categories", CategoriesService),
],
)
def test_integration_properties(integration, property_name, expected_service_class):
Expand All @@ -51,6 +56,7 @@ def test_integration_properties(integration, property_name, expected_service_cla
("property_name", "expected_service_class"),
[
("extensions", AsyncExtensionsService),
("categories", AsyncCategoriesService),
],
)
def test_async_integration_properties(async_integration, property_name, expected_service_class):
Expand Down