Skip to content

Commit 7433ffc

Browse files
author
Robert Segal
committed
Seeded accounts e2e data
1 parent 1e68515 commit 7433ffc

33 files changed

Lines changed: 1276 additions & 132 deletions

mpt_api_client/resources/commerce/assets.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ class AssetTemplate(Model):
2121
"""Asset template resource."""
2222

2323

24-
class AssetsServiceConfig:
24+
class AssetServiceConfig:
2525
"""Assets service config."""
2626

2727
_endpoint = "/public/v1/commerce/assets"
2828
_model_class = Asset
2929
_collection_key = "data"
3030

3131

32-
class AssetsService(
32+
class AssetService(
3333
CreateMixin[Asset],
3434
UpdateMixin[Asset],
3535
GetMixin[Asset],
3636
CollectionMixin[Asset],
3737
Service[Asset],
38-
AssetsServiceConfig,
38+
AssetServiceConfig,
3939
):
4040
"""Assets service."""
4141

@@ -63,13 +63,13 @@ def render(self, asset_id: str) -> AssetTemplate:
6363
return AssetTemplate.from_response(response)
6464

6565

66-
class AsyncAssetsService(
66+
class AsyncAssetService(
6767
AsyncCreateMixin[Asset],
6868
AsyncUpdateMixin[Asset],
6969
AsyncGetMixin[Asset],
7070
AsyncCollectionMixin[Asset],
7171
AsyncService[Asset],
72-
AssetsServiceConfig,
72+
AssetServiceConfig,
7373
):
7474
"""Asynchronous Assets service."""
7575

mpt_api_client/resources/commerce/commerce.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from mpt_api_client.http import AsyncHTTPClient, HTTPClient
22
from mpt_api_client.resources.commerce.agreements import AgreementsService, AsyncAgreementsService
3-
from mpt_api_client.resources.commerce.assets import AssetsService, AsyncAssetsService
3+
from mpt_api_client.resources.commerce.assets import AssetService, AsyncAssetService
44
from mpt_api_client.resources.commerce.orders import AsyncOrdersService, OrdersService
55
from mpt_api_client.resources.commerce.subscriptions import (
66
AsyncSubscriptionsService,
@@ -30,9 +30,9 @@ def subscriptions(self) -> SubscriptionsService:
3030
return SubscriptionsService(http_client=self.http_client)
3131

3232
@property
33-
def assets(self) -> AssetsService:
33+
def assets(self) -> AssetService:
3434
"""Asset service."""
35-
return AssetsService(http_client=self.http_client)
35+
return AssetService(http_client=self.http_client)
3636

3737

3838
class AsyncCommerce:
@@ -57,6 +57,6 @@ def subscriptions(self) -> AsyncSubscriptionsService:
5757
return AsyncSubscriptionsService(http_client=self.http_client)
5858

5959
@property
60-
def assets(self) -> AsyncAssetsService:
60+
def assets(self) -> AsyncAssetService:
6161
"""Asset service."""
62-
return AsyncAssetsService(http_client=self.http_client)
62+
return AsyncAssetService(http_client=self.http_client)

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dev = [
3434
"ipython==9.*",
3535
"mypy==1.15.*",
3636
"pre-commit==4.2.*",
37+
"pyfakefs==5.10.*",
3738
"pytest==8.3.*",
3839
"pytest-asyncio==1.2.*",
3940
"pytest-cov==6.1.*",
@@ -114,7 +115,7 @@ per-file-ignores = [
114115
"mpt_api_client/http/mixins.py: WPS202 WPS204 WPS235",
115116
"mpt_api_client/resources/*: WPS215",
116117
"mpt_api_client/models/model.py: WPS215 WPS110",
117-
"mpt_api_client/resources/accounts/*.py: WPS202 WPS215 WPS214 WPS235",
118+
"mpt_api_client/resources/accounts/*.py: WPS202 WPS215 WPS214 WPS235 WPS453",
118119
"mpt_api_client/resources/billing/*.py: WPS202 WPS204 WPS214 WPS215",
119120
"mpt_api_client/resources/catalog/*.py: WPS110 WPS214 WPS215 WPS235",
120121
"mpt_api_client/resources/catalog/mixins.py: WPS110 WPS202 WPS214 WPS215 WPS235",
@@ -130,7 +131,8 @@ per-file-ignores = [
130131
"tests/e2e/accounts/*.py: WPS430 WPS202",
131132
"tests/e2e/catalog/*.py: WPS202 WPS421",
132133
"tests/e2e/catalog/items/*.py: WPS110 WPS202",
133-
"tests/*: WPS432 WPS202"
134+
"tests/*: WPS432 WPS202",
135+
"seed/accounts/*.py: WPS453",
134136
]
135137

136138
[tool.ruff]

seed/accounts/accounts.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import logging
2+
3+
from seed.accounts.api_tokens import seed_api_token
4+
from seed.accounts.buyer import seed_buyer
5+
from seed.accounts.licensee import seed_licensee
6+
from seed.accounts.module import seed_module
7+
from seed.accounts.seller import seed_seller
8+
from seed.accounts.user_group import seed_user_group
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
async def seed_accounts() -> None: # noqa: WPS217
14+
"""Seed accounts data including account."""
15+
logger.debug("Seeding accounts ...")
16+
await seed_seller()
17+
await seed_buyer()
18+
await seed_module()
19+
await seed_api_token()
20+
await seed_user_group()
21+
await seed_licensee()
22+
23+
logger.debug("Seeded accounts completed.")

seed/accounts/api_tokens.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import logging
2+
import os
3+
4+
from dependency_injector.wiring import inject
5+
6+
from mpt_api_client import AsyncMPTClient
7+
from mpt_api_client.resources.accounts.api_tokens import ApiToken
8+
from seed.context import Context
9+
from seed.defaults import DEFAULT_CONTEXT, DEFAULT_MPT_OPERATIONS
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
@inject
15+
async def get_api_token(
16+
context: Context = DEFAULT_CONTEXT,
17+
mpt_ops: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
18+
) -> ApiToken | None:
19+
"""Get API token from context or fetch from API."""
20+
api_token_id = context.get_string("accounts.api_token.id")
21+
if not api_token_id:
22+
return None
23+
try:
24+
api_token = context.get_resource("accounts.api_token", api_token_id)
25+
except ValueError:
26+
api_token = None
27+
if not isinstance(api_token, ApiToken):
28+
api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
29+
context.set_resource("accounts.api_token", api_token)
30+
context["accounts.api_token.id"] = api_token.id
31+
return api_token
32+
return api_token
33+
34+
35+
@inject
36+
def build_api_token_data(
37+
context: Context = DEFAULT_CONTEXT,
38+
) -> dict[str, object]:
39+
"""Get API token data dictionary for creation."""
40+
account_id = os.getenv("CLIENT_ACCOUNT_ID")
41+
module_id = context.get_string("accounts.module.id")
42+
return {
43+
"account": {"id": account_id},
44+
"name": "E2E Seeded API Token",
45+
"description": "This is a seeded API token for end-to-end testing.",
46+
"icon": "",
47+
"modules": [{"id": module_id}],
48+
}
49+
50+
51+
@inject
52+
async def init_api_token(
53+
context: Context = DEFAULT_CONTEXT,
54+
mpt_ops: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
55+
) -> ApiToken:
56+
"""Get or create API token."""
57+
api_token = await get_api_token(context=context, mpt_ops=mpt_ops)
58+
if api_token is None:
59+
logger.debug("Creating API token ...")
60+
api_token_data = build_api_token_data(context=context)
61+
api_token = await mpt_ops.accounts.api_tokens.create(api_token_data)
62+
context.set_resource("accounts.api_token", api_token)
63+
context["accounts.api_token.id"] = api_token.id
64+
logger.info("API token created: %s", api_token.id)
65+
else:
66+
logger.info("API token found: %s", api_token.id)
67+
return api_token
68+
69+
70+
@inject
71+
async def seed_api_token() -> None:
72+
"""Seed API token."""
73+
logger.debug("Seeding API token ...")
74+
await init_api_token()
75+
logger.debug("Seeding API token completed.")

seed/accounts/buyer.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import logging
2+
import os
3+
import pathlib
4+
5+
from dependency_injector.wiring import inject
6+
7+
from mpt_api_client import AsyncMPTClient
8+
from mpt_api_client.resources.accounts.buyers import Buyer
9+
from seed.context import Context
10+
from seed.defaults import DEFAULT_CONTEXT, DEFAULT_MPT_OPERATIONS
11+
12+
logger = logging.getLogger(__name__)
13+
14+
icon = pathlib.Path("seed/data/logo.png").resolve()
15+
16+
17+
@inject
18+
async def get_buyer(
19+
context: Context = DEFAULT_CONTEXT,
20+
mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
21+
) -> Buyer | None:
22+
"""Get buyer from context or fetch from API."""
23+
buyer_id = context.get_string("accounts.buyer.id")
24+
if not buyer_id:
25+
return None
26+
try:
27+
buyer = context.get_resource("accounts.buyer", buyer_id)
28+
except ValueError:
29+
buyer = None
30+
if not isinstance(buyer, Buyer):
31+
buyer = await mpt_operations.accounts.buyers.get(buyer_id)
32+
context.set_resource("accounts.buyer", buyer)
33+
context["accounts.buyer.id"] = buyer.id
34+
return buyer
35+
return buyer
36+
37+
38+
@inject
39+
def build_buyer_data(context: Context = DEFAULT_CONTEXT) -> dict[str, object]:
40+
"""Build buyer data dictionary for creation."""
41+
buyer_account_id = os.getenv("CLIENT_ACCOUNT_ID")
42+
if not buyer_account_id:
43+
raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
44+
seller_id = context.get_string("accounts.seller.id")
45+
if not seller_id:
46+
raise ValueError("accounts.seller.id missing from context; seed seller before buyer.")
47+
return {
48+
"name": "E2E Seeded Buyer",
49+
"account": {"id": buyer_account_id},
50+
"sellers": [{"id": seller_id}],
51+
"contact": {
52+
"firstName": "first",
53+
"lastName": "last",
54+
"email": "created.buyer@example.com",
55+
},
56+
"address": {
57+
"addressLine1": "123 Main St",
58+
"city": "Los Angeles",
59+
"state": "CA",
60+
"postCode": "12345",
61+
"country": "US",
62+
},
63+
}
64+
65+
66+
@inject
67+
async def init_buyer(
68+
context: Context = DEFAULT_CONTEXT,
69+
mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
70+
) -> Buyer:
71+
"""Get or create buyer."""
72+
buyer = await get_buyer(context=context, mpt_operations=mpt_operations)
73+
if buyer is None:
74+
buyer_data = build_buyer_data(context=context)
75+
logger.debug("Creating buyer ...")
76+
with open(str(icon), "rb") as icon_file: # noqa: PTH123
77+
created = await mpt_operations.accounts.buyers.create(buyer_data, file=icon_file)
78+
if isinstance(created, Buyer):
79+
context.set_resource("accounts.buyer", created)
80+
context["accounts.buyer.id"] = created.id
81+
logger.info("Buyer created: %s", created.id)
82+
return created
83+
logger.warning("Buyer creation failed")
84+
raise ValueError("Buyer creation failed")
85+
logger.info("Buyer found: %s", buyer.id)
86+
return buyer
87+
88+
89+
@inject
90+
async def seed_buyer() -> None:
91+
"""Seed buyer."""
92+
logger.debug("Seeding buyer ...")
93+
await init_buyer()
94+
logger.debug("Seeding buyer completed.")

seed/accounts/licensee.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import logging
2+
import os
3+
import pathlib
4+
5+
from dependency_injector.wiring import inject
6+
7+
from mpt_api_client import AsyncMPTClient
8+
from mpt_api_client.resources.accounts.licensees import Licensee
9+
from seed.context import Context
10+
from seed.defaults import DEFAULT_CONTEXT, DEFAULT_MPT_CLIENT
11+
12+
logger = logging.getLogger(__name__)
13+
14+
icon = pathlib.Path("seed/data/logo.png").resolve()
15+
16+
17+
@inject
18+
async def get_licensee(
19+
context: Context = DEFAULT_CONTEXT,
20+
mpt_client: AsyncMPTClient = DEFAULT_MPT_CLIENT,
21+
) -> Licensee | None:
22+
"""Get licensee from context or fetch from API."""
23+
licensee_id = context.get_string("accounts.licensee.id")
24+
if not licensee_id:
25+
return None
26+
try:
27+
licensee = context.get_resource("accounts.licensee", licensee_id)
28+
except ValueError:
29+
licensee = None
30+
if not isinstance(licensee, Licensee):
31+
licensee = await mpt_client.accounts.licensees.get(licensee_id)
32+
context.set_resource("accounts.licensee", licensee)
33+
context["accounts.licensee.id"] = licensee.id
34+
return licensee
35+
return licensee
36+
37+
38+
@inject
39+
def build_licensee_data( # noqa: WPS238
40+
context: Context = DEFAULT_CONTEXT,
41+
) -> dict[str, object]:
42+
"""Get licensee data dictionary for creation."""
43+
account_id = os.getenv("CLIENT_ACCOUNT_ID")
44+
if not account_id:
45+
raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
46+
seller_id = context.get_string("accounts.seller.id")
47+
if not seller_id:
48+
raise ValueError("Seller ID is required in context")
49+
buyer_id = context.get_string("accounts.buyer.id")
50+
if not buyer_id:
51+
raise ValueError("Buyer ID is required in context")
52+
group = context.get_resource("accounts.user_group")
53+
if group is None:
54+
raise ValueError("User group is required in context")
55+
licensee_type = "Client"
56+
return {
57+
"name": "E2E Seeded Licensee",
58+
"address": {
59+
"addressLine1": "123 Main St",
60+
"city": "Los Angeles",
61+
"state": "CA",
62+
"postCode": "67890",
63+
"country": "US",
64+
},
65+
"useBuyerAddress": False,
66+
"seller": {"id": seller_id},
67+
"buyer": {"id": buyer_id},
68+
"account": {"id": account_id},
69+
"eligibility": {"client": True, "partner": False},
70+
"groups": [{"id": group.id}],
71+
"type": licensee_type,
72+
"status": "Enabled",
73+
"defaultLanguage": "en-US",
74+
}
75+
76+
77+
@inject
78+
async def init_licensee(
79+
context: Context = DEFAULT_CONTEXT,
80+
mpt_client: AsyncMPTClient = DEFAULT_MPT_CLIENT,
81+
) -> Licensee:
82+
"""Get or create licensee."""
83+
licensee = await get_licensee(context=context, mpt_client=mpt_client)
84+
if licensee is None:
85+
licensee_data = build_licensee_data(context=context)
86+
logger.debug("Creating licensee ...")
87+
with open(str(icon), "rb") as icon_file: # noqa: PTH123
88+
created = await mpt_client.accounts.licensees.create(licensee_data, file=icon_file)
89+
if isinstance(created, Licensee):
90+
context.set_resource("accounts.licensee", created)
91+
context["accounts.licensee.id"] = created.id
92+
logger.info("Licensee created: %s", created.id)
93+
return created
94+
logger.warning("Licensee creation failed")
95+
raise ValueError("Licensee creation failed")
96+
logger.info("Licensee found: %s", licensee.id)
97+
return licensee
98+
99+
100+
@inject
101+
async def seed_licensee() -> None:
102+
"""Seed licensee."""
103+
logger.debug("Seeding licensee ...")
104+
await init_licensee()
105+
logger.info("Seeding licensee completed.")

0 commit comments

Comments
 (0)