Skip to content

Conversation

@robcsegal
Copy link
Contributor

@robcsegal robcsegal commented Dec 3, 2025

Seeded accounts e2e data
https://softwareone.atlassian.net/browse/MPT-15443

Summary by CodeRabbit

  • New Features

    • Adds full account seeding (seller, buyer, module, API token, user group, licensee) and runs catalog and accounts seeding concurrently.
  • Bug Fixes

    • Product initialization and review now correctly reuse existing products and only create/review when appropriate.
  • Tests

    • Adds extensive unit and e2e tests and fixtures for account seeding; updates fixture addresses/cities; adds fake-file and client test helpers; adopts mocker and pyfakefs in tests.
  • Chores

    • Lint config updated to suppress new per-file warnings; dev dependency for filesystem testing added.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

Walkthrough

Adds a new accounts seeding subsystem (seller, buyer, module, api token, user group, licensee) with per-entity get/build/init/seed functions, wires them into the DI container, runs accounts seeding concurrently with catalog, updates product seeding to consult context, adds tests and pyfakefs dev dependency.

Changes

Cohort / File(s) Change Summary
Accounts orchestration
\seed/accounts/accounts.py``
New async seed_accounts() orchestrator that sequentially awaits per-entity seeders and logs start/completion.
Accounts seed modules
\seed/accounts/*.py`<br>`seed/accounts/seller.py`, `seed/accounts/buyer.py`, `seed/accounts/module.py`, `seed/accounts/api_tokens.py`, `seed/accounts/user_group.py`, `seed/accounts/licensee.py``
New modules implementing get/build/init/seed patterns using dependency injection, Context caching, AsyncMPTClient calls, and file I/O for icons/logos where needed; expose public get/build/init/seed functions and logging.
DI container wiring
\seed/container.py``
Added wiring entries for seed.accounts and its submodules to the container wiring list.
Seed pipeline
\seed/seed_api.py``
Imports seed_accounts and runs catalog and accounts seeding concurrently via typed asyncio Tasks; adds try/except logging and finally saves context.
Catalog product logic
\seed/catalog/product.py``
init_product and review_product now consult get_product(context=...) before creating or reviewing; creation uses icon file I/O.
Tests — seed accounts
\tests/seed/accounts/*.py`<br>`tests/seed/accounts/test_accounts.py`, `tests/seed/accounts/test_seller.py`, `tests/seed/accounts/test_buyer.py`, `tests/seed/accounts/test_module.py`, `tests/seed/accounts/test_api_token.py`, `tests/seed/accounts/test_user_group.py`, `tests/seed/accounts/test_licensee.py``
New unit tests and fixtures covering get/build/init/seed flows, Context caching, file I/O mocks, and assertions that async seeders are awaited; use AsyncMock/patching/pytest fixtures.
Tests — infra & product tests
\tests/seed/conftest.py`, `tests/seed/catalog/test_product.py`, `tests/seed/test_seed_api.py``
Added fake_file / fake_file_factory and client_client fixtures; switched product tests to use mocker/pyfakefs and updated seed_api test to assert seed_accounts invocation.
E2E fixture updates
\tests/e2e/accounts/buyers/conftest.py`, `tests/e2e/accounts/sellers/conftest.py`, `tests/e2e/accounts/licensees/conftest.py``
Small fixture data tweaks: buyer/seller city → "Los Angeles"; licensee addressLine1 → "123 Main St".
API client renames & tests
\mpt_api_client/resources/commerce/assets.py`, `mpt_api_client/resources/commerce/commerce.py`, tests/...\
Renamed AssetsService/AsyncAssetsServiceAssetService/AsyncAssetService; updated Commerce properties and tests to use new names.
Lint & deps
\pyproject.toml``
Added pyfakefs==5.10.* to dev dependencies and added WPS453 per-file-ignores for seed/accounts/*.py.
Test cleanup/removals
\tests/seed/catalog/conftest.py``
Removed prior fixtures (replaced by consolidated fixtures in tests/seed/conftest.py).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant SeedAPI as seed_api()
    participant Accounts as seed_accounts()
    participant Seller as seed_seller()
    participant Buyer as seed_buyer()
    participant Module as seed_module()
    participant APIToken as seed_api_token()
    participant UserGroup as seed_user_group()
    participant Licensee as seed_licensee()
    participant Context as Context
    participant MPT as AsyncMPTClient

    SeedAPI->>+Accounts: start accounts seeding (concurrent with catalog)
    Accounts->>+Seller: await seed_seller()
    Seller->>MPT: get/create seller
    MPT-->>Seller: Seller resource
    Seller->>Context: store seller id/resource
    Accounts->>+Buyer: await seed_buyer()
    Buyer->>MPT: get/create buyer (may upload logo)
    MPT-->>Buyer: Buyer resource
    Buyer->>Context: store buyer id/resource
    Accounts->>+Module: await seed_module()
    Module->>MPT: get or filter modules
    MPT-->>Module: Module resource(s)
    Module->>Context: store module id/resource
    Accounts->>+APIToken: await seed_api_token()
    APIToken->>MPT: get/create api token
    MPT-->>APIToken: ApiToken resource
    APIToken->>Context: store token id/resource
    Accounts->>+UserGroup: await seed_user_group()
    UserGroup->>MPT: get/create user group (refs modules/buyers)
    MPT-->>UserGroup: UserGroup resource
    UserGroup->>Context: store user_group id/resource
    Accounts->>+Licensee: await seed_licensee()
    Licensee->>MPT: get/create licensee (may upload icon)
    MPT-->>Licensee: Licensee resource
    Licensee->>Context: store licensee id/resource
    Accounts-->>-SeedAPI: accounts seeding complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas for review:
    • Consistency of Context keys and resource caching across new account modules.
    • Async correctness and proper awaiting of AsyncMPTClient calls and test AsyncMock usage.
    • File I/O (Path.open) handling and compatibility with pyfakefs/fake_file fixtures.
    • Concurrency and exception handling in seed/seed_api.py and final save_context logic.
    • DI wiring in seed/container.py and potential import/circular dependency risks.

Possibly related PRs

Suggested reviewers

  • d3rky
  • alephsur
  • ruben-sebrango
  • svazquezco
  • jentyk

Poem

🐇
I hopped through files and left a trail of seeds,
Sellers, buyers, modules — tended tiny deeds.
Async whiskers twitch as logos slip inside,
Tests hum like carrots keeping bugs outside.
A nibble, a commit — then back to the greens.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.20% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'MPT-15443 Seeded accounts e2e data' accurately reflects the primary change in the PR, which introduces comprehensive account seeding functionality for end-to-end testing.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch MPT-15443-seed-data-required-for-accounts-e-2-e-tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Dec 3, 2025

Warnings
⚠️

This PR changes 1428 lines across 33 files (threshold: 600). Please consider splitting it into smaller PRs for easier review.

⚠️

Some newly added source files do not have corresponding tests in the tests/ folder with matching structure and the test_ prefix.

✅ Found Jira issue key in the title: MPT-15443

Tests mirroring check (created files only)

Added source file Expected test (added in this PR)
seed/accounts/accounts.py tests/accounts/test_accounts.py
seed/accounts/api_tokens.py tests/accounts/test_api_tokens.py
seed/accounts/buyer.py tests/accounts/test_buyer.py
seed/accounts/licensee.py tests/accounts/test_licensee.py
seed/accounts/module.py tests/accounts/test_module.py
seed/accounts/seller.py tests/accounts/test_seller.py
seed/accounts/user_group.py tests/accounts/test_user_group.py

Generated by 🚫 dangerJS against 52f78a0

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (20)
seed/accounts/api_tokens.py (2)

40-48: Missing validation for required environment variable.

os.getenv("CLIENT_ACCOUNT_ID") returns None if the variable isn't set, which will pass {"id": None} to the API. Consider adding validation or using a default/error handling.

 @inject
 def build_api_token_data(
     context: Context = DEFAULT_CONTEXT,
 ) -> dict[str, object]:
     """Get API token data dictionary for creation."""
     account_id = os.getenv("CLIENT_ACCOUNT_ID")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
     module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError("accounts.module.id must be set in context")
     return {

27-32: Consider validating API response before accessing .id.

After fetching from the API on line 28, the code accesses api_token.id without verifying the response type. If the API returns an unexpected type, this could raise an AttributeError.

     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
+        if not isinstance(api_token, ApiToken):
+            return None
         context.set_resource("accounts.api_token", api_token)
         context["accounts.api_token.id"] = api_token.id
-        return api_token
-    return api_token
+    return api_token
seed/accounts/buyer.py (2)

62-83: Consider forwarding injected dependencies explicitly.

When init_buyer is called with explicit context or mpt_operations parameters (bypassing DI defaults), those values won't propagate to the nested get_buyer() and build_buyer_data() calls on lines 68 and 70. This could cause inconsistencies if callers expect explicit parameters to be honored throughout the call chain.

If explicit parameter forwarding is desired, apply this diff:

 async def init_buyer(
     context: Context = DEFAULT_CONTEXT,
     mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
 ) -> Buyer:
     """Get or create buyer."""
-    buyer = await get_buyer()
+    buyer = await get_buyer(context=context, mpt_operations=mpt_operations)
     if buyer is None:
-        buyer_data = build_buyer_data()
+        buyer_data = build_buyer_data(context=context)
         logger.debug("Creating buyer ...")
         with pathlib.Path.open(icon, "rb") as icon_file:
-            created = await mpt_operations.accounts.buyers.create(buyer_data, file=icon_file)
+            created = await mpt_operations.accounts.buyers.create(buyer_data, file=icon_file)

81-81: Refactor exception message per linting guideline.

The static analysis tool suggests avoiding long exception messages inline. Extract to a constant or use a predefined exception class.

Apply this diff:

-        logger.warning("Buyer creation failed")
-        raise ValueError("Buyer creation failed")
+        error_msg = "Buyer creation failed"
+        logger.warning(error_msg)
+        raise ValueError(error_msg)
seed/accounts/user_group.py (2)

52-67: Consider forwarding injected dependencies explicitly.

The same DI parameter forwarding issue exists here as in buyer.py. When init_user_group is called with explicit parameters, they won't propagate to get_user_group() (line 58) and build_user_group_data() (line 61).

If explicit forwarding is desired:

 async def init_user_group(
     context: Context = DEFAULT_CONTEXT,
     mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
 ) -> UserGroup:
     """Get or create user group."""
-    user_group = await get_user_group()
+    user_group = await get_user_group(context=context, mpt_operations=mpt_operations)
     if user_group is None:
         logger.debug("Creating user group ...")
-        user_group_data = build_user_group_data()
+        user_group_data = build_user_group_data(context=context)
         user_group = await mpt_operations.accounts.user_groups.create(user_group_data)

70-76: Align return type with other seed functions.

seed_user_group returns UserGroup, while seed_buyer in buyer.py (line 87) returns None. This inconsistency may confuse callers about the expected interface of seed functions.

Consider standardizing return types. If orchestration doesn't need the return value, prefer None:

 @inject
-async def seed_user_group() -> UserGroup:
+async def seed_user_group() -> None:
     """Seed user group."""
     logger.debug("Seeding user group ...")
-    user_group = await init_user_group()
+    await init_user_group()
     logger.debug("Seeding user group completed.")
-    return user_group
tests/seed/accounts/test_accounts.py (1)

6-30: Remove unused noqa directive.

The static analysis tool correctly identifies that the WPS210 noqa directive on line 6 is unused and can be removed.

Apply this diff:

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:
     with (

Otherwise, the test properly verifies that all six seed functions are invoked by the orchestrator.

seed/accounts/accounts.py (1)

13-13: Remove unused noqa directive.

The WPS217 code is from wemake-python-styleguide, which isn't recognized by Ruff. If you're not using wemake-python-styleguide in this project, remove the directive.

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
tests/seed/accounts/test_module.py (4)

17-21: Chain the exception for proper traceback.

Per Ruff B904, when re-raising a different exception type inside an except clause, chain it with from to preserve the traceback context.

     async def __anext__(self):
         try:
             return next(self._iterator)
         except StopIteration:
-            raise StopAsyncIteration
+            raise StopAsyncIteration from None

34-36: Remove unused function.

The async_iter function is not used anywhere in this test file. Additionally, despite its name, it's a synchronous generator (uses yield from), not an async iterator.

-def async_iter(iter_items):
-    yield from iter_items
-

72-87: Consider removing duplicate test.

test_refresh_module_get_new appears to be a duplicate of test_refresh_module (lines 54-69). Both tests:

  • Patch get_module to return None
  • Mock filter().iterate() to return a DummyAsyncIterator([module])
  • Assert result == module

If these tests are intended to cover different scenarios, add distinguishing assertions or comments. Otherwise, consolidate into a single test.


90-99: Consider adding test coverage for additional paths.

The test only covers the path where get_module returns None and refresh_module succeeds. Per the seed_module implementation, consider adding tests for:

  1. When get_module returns an existing module (should skip refresh_module).
  2. When refresh_module returns None (should raise ValueError).
tests/seed/accounts/test_buyer.py (1)

89-97: Consider using fake_file_factory for consistent file mocking.

The Path.open mock returns AsyncMock(), but file operations expect a synchronous file-like object. Other tests in this PR (e.g., test_product.py) use the fake_file_factory fixture for this purpose.

If init_buyer reads the file contents, this mock may cause issues. Consider using the consistent pattern:

+async def test_init_buyer_create_new(
+    context: Context, operations_client, buyers_service, buyer, fake_file_factory
+) -> None:
     ...
     with (
         patch("seed.accounts.buyer.get_buyer", return_value=None),
-        patch("pathlib.Path.open", return_value=AsyncMock()),
+        patch("pathlib.Path.open", new=lambda *args, **kwargs: fake_file_factory()),
         patch("seed.accounts.buyer.build_buyer_data", return_value=build_buyer_data(context)),
     ):
tests/seed/accounts/test_api_token.py (1)

73-91: Duplicate test - consolidate or differentiate.

test_init_api_token_create_new is identical to test_init_api_token (lines 40-54). Both patch get_api_token to return None and assert create is called once.

Consider:

  1. Removing the duplicate, or
  2. Converting one test to cover the "existing token found" path (where get_api_token returns the token and create should not be called)
tests/seed/catalog/test_product.py (1)

43-55: Good refactoring to use fake_file_factory.

Using the shared fake_file_factory fixture for file mocking improves consistency across tests.

Optional: To silence the Ruff warning about unused lambda arguments, you can use underscore convention:

-        patch("pathlib.Path.open", new=lambda *args, **kwargs: fake_file_factory()),
+        patch("pathlib.Path.open", new=lambda *_args, **_kwargs: fake_file_factory()),
tests/seed/accounts/test_user_group.py (1)

64-78: Clarify the contract for build_user_group_data vs. how this test sets up context

test_build_user_group_data sets context["accounts.account.id"], but the current implementation of build_user_group_data (per snippet) pulls the account id from os.getenv("CLIENT_ACCOUNT_ID"), not from the context. The test only passes because the env var happens to match the hard‑coded "ACC-1086-6867".

To avoid confusion for future readers, consider either:

  • Updating build_user_group_data to derive the account id from the context (to match what the test is setting up), or
  • Explicitly documenting in the test (or via a fixture) that the account id comes from CLIENT_ACCOUNT_ID and dropping the unused context["accounts.account.id"] assignment.

Right now the test implies the context value is used when it is not.

seed/accounts/module.py (2)

40-57: refresh_module docstring doesn’t quite match behavior

The docstring says “always fetch”, but the function only falls back to the filter(...).iterate() path when get_module returns None; if a module is already in context, it just returns that cached instance without re‑fetching.

Consider rewording the docstring to something like “Ensure a module is present in context, fetching by name if missing” to avoid confusion.


61-72: Ruff TRY003 on the ValueError message

Static analysis is flagging the long error message on Line 69 (TRY003). To keep CI happy and make the message more concise, you could either shorten it or move the detail into a dedicated exception type.

For a minimal change that should satisfy TRY003, something like:

-        if refreshed is None:
-            raise ValueError("Could not seed module: no valid Module found.")
+        if refreshed is None:
+            raise ValueError("No valid module found to seed.")

If you prefer, you can also introduce a custom exception class (e.g., ModuleSeedError) and raise that instead.

tests/seed/accounts/test_licensee.py (1)

83-99: Ensure init_licensee forwards context/mpt_client to its helpers

These tests correctly exercise the “create if missing” paths by patching get_licensee and build_licensee_data, but they don’t assert how those helpers are called. In similar seed modules (e.g., seller/user_group), it’s easy to accidentally write:

licensee = await get_licensee()         # uses defaults
licensee_data = build_licensee_data()   # uses defaults

inside init_licensee, even though init_licensee itself accepts context and mpt_client. If that happens, callers passing a custom context/client into init_licensee won’t have those dependencies honored.

I suggest:

  • Double‑checking that init_licensee calls:
    • await get_licensee(context=context, mpt_client=mpt_client)
    • build_licensee_data(context=context)
  • Optionally, updating one of these tests to assert the helper call signatures, e.g. using new_callable=AsyncMock for get_licensee and assert_awaited_with(context=context, mpt_client=client_client).

This will align the licensee seeding path with the rest of the DI‑aware seed modules and guard against subtle context mismatches.

Also applies to: 102-119

tests/seed/accounts/test_seller.py (1)

56-63: Consider asserting how get_seller/build_seller_data are called to lock in DI behavior

These tests correctly exercise the “create if missing” paths by patching get_seller and build_seller_data and verifying that sellers_service.create is called and the result is returned.

Given init_seller accepts context and mpt_operations, and we want it to forward those into get_seller, you could make the tests more robust by also asserting the helper calls, for example:

with patch("seed.accounts.seller.get_seller", new_callable=AsyncMock) as mock_get:
    mock_get.return_value = None
    result = await init_seller(context=context, mpt_operations=operations_client)
    mock_get.assert_awaited_with(context=context, mpt_operations=operations_client)

This will catch any future regressions where init_seller might accidentally stop honoring the injected dependencies.

Also applies to: 66-80

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c24cde9 and 85ab0a4.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (23)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-27T00:05:36.332Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/conftest.py:0-0
Timestamp: 2025-11-27T00:05:36.332Z
Learning: When reviewing PRs that add fixtures to a parent conftest that may duplicate fixtures in child conftest files, do not suggest removing the duplicates from child conftest files that are outside the scope of the PR's changes.

Applied to files:

  • tests/seed/conftest.py
📚 Learning: 2025-11-27T00:05:54.691Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.691Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/conftest.py
  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (15)
seed/accounts/seller.py (2)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/licensee.py (4)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_licensee.py (1)
  • licensee (12-30)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_api_token.py (2)
seed/accounts/api_tokens.py (4)
  • build_api_token_data (36-48)
  • get_api_token (15-32)
  • init_api_token (52-67)
  • seed_api_token (71-75)
seed/context.py (1)
  • get_resource (19-29)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (87-91)
seed/accounts/licensee.py (1)
  • seed_licensee (93-97)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (73-77)
seed/accounts/user_group.py (1)
  • seed_user_group (71-76)
tests/seed/accounts/test_module.py (2)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/user_group.py (6)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/e2e/conftest.py (1)
  • logger (63-64)
tests/e2e/accounts/conftest.py (1)
  • user_group_id (37-38)
tests/seed/accounts/test_user_group.py (1)
  • user_group (16-17)
tests/seed/conftest.py (1)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
seed/catalog/product.py (6)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
tests/e2e/conftest.py (1)
  • mpt_vendor (19-20)
mpt_api_client/http/mixins.py (6)
  • create (18-26)
  • create (86-112)
  • create (118-144)
  • create (190-216)
  • create (262-270)
  • create (331-358)
mpt_api_client/resources/catalog/products.py (1)
  • Product (49-50)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_user_group.py (3)
seed/accounts/user_group.py (4)
  • build_user_group_data (36-49)
  • get_user_group (15-32)
  • init_user_group (53-67)
  • seed_user_group (71-76)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/module.py (5)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_seller.py (2)
seed/accounts/seller.py (4)
  • build_seller_data (36-51)
  • get_seller (15-32)
  • init_seller (55-69)
  • seed_seller (73-77)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/buyer.py (3)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/seed_api.py (4)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
tests/seed/conftest.py (1)
  • context (15-16)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
tests/seed/catalog/test_product.py (6)
tests/seed/conftest.py (3)
  • context (15-16)
  • vendor_client (20-21)
  • fake_file_factory (35-36)
seed/context.py (1)
  • Context (12-33)
tests/seed/catalog/test_item_group.py (1)
  • vendor_client (29-30)
tests/seed/catalog/test_product_parameters.py (1)
  • vendor_client (30-31)
seed/catalog/product.py (1)
  • init_product (45-65)
tests/e2e/conftest.py (1)
  • mpt_vendor (19-20)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
🪛 Ruff (0.14.7)
seed/accounts/licensee.py

87-87: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_module.py

21-21: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/buyer.py

81-81: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/catalog/test_product.py

51-51: Unused lambda argument: args

(ARG005)


51-51: Unused lambda argument: kwargs

(ARG005)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (25)
tests/e2e/accounts/sellers/conftest.py (1)

20-20: LGTM!

Test data alignment to use "Los Angeles" consistently across fixtures is appropriate.

seed/catalog/product.py (1)

51-65: Clear control flow with proper resource management.

The restructured logic cleanly separates the creation path from the retrieval path. The file context manager properly handles the icon file.

Minor style consideration: The static analysis tool flags the inline exception message on line 63 (TRY003). If you have a project convention for custom exception classes, you could define one, but for seed scripts this is acceptable.

seed/accounts/api_tokens.py (1)

51-67: LGTM!

The get-or-create pattern is consistent with other seed modules. Logging at appropriate levels.

seed/accounts/licensee.py (3)

69-89: Solid get-or-create implementation with proper file handling.

The context manager for file I/O and isinstance validation after creation are good practices. The pattern is consistent with init_product in the catalog module.

The static analysis hint (TRY003) on line 87 is a minor style consideration that can be addressed optionally.


17-35: Same pattern as get_api_token - consistent structure.

Note: The same optional validation suggestion from api_tokens.py applies here for the API response on line 31.


43-46: Add validation for CLIENT_ACCOUNT_ID environment variable.

Line 43: os.getenv("CLIENT_ACCOUNT_ID") may return None if the environment variable is not set. This None value is then passed to the API payload at line 60 ("account": {"id": account_id}). Consider validating that the environment variable is set before use, or provide a default/fallback value.

tests/e2e/accounts/licensees/conftest.py (1)

35-35: LGTM!

Test fixture data aligned with seed module (seed/accounts/licensee.py) for consistency.

seed/accounts/buyer.py (2)

17-35: LGTM!

The retrieval logic correctly handles context caching and API fallback with appropriate type checking.


86-91: LGTM!

The seed orchestration function is clear and appropriately delegates to init_buyer.

seed/accounts/user_group.py (1)

14-32: LGTM!

The retrieval logic correctly handles context caching and API fallback.

tests/seed/test_seed_api.py (1)

23-39: LGTM!

The test correctly patches and asserts the new seed_accounts call, maintaining consistency with the existing catalog test pattern.

tests/e2e/accounts/buyers/conftest.py (1)

32-32: LGTM!

Test data alignment with the seeded buyer data.

seed/container.py (1)

60-67: LGTM!

The DI wiring correctly includes all new accounts seed modules, enabling proper dependency injection across the accounts seeding workflow.

seed/seed_api.py (1)

17-33: LGTM!

The concurrent execution of catalog and accounts seeding with proper error handling and guaranteed context persistence is well-implemented.

tests/seed/conftest.py (1)

10-11: LGTM!

The test utilities provide clean abstractions for file I/O mocking and client mocking, supporting the new accounts seed test suite.

Also applies to: 29-36

seed/accounts/accounts.py (1)

15-23: LGTM!

The sequential orchestration of seed functions follows a logical dependency order. The implementation correctly coordinates the seeding of all account-related entities.

Minor nit: The completion log says "Seeded accounts completed" while the start log says "Seeding accounts ...". Consider using consistent phrasing like "Seeding accounts completed." for symmetry.

tests/seed/accounts/test_buyer.py (2)

36-60: Verify the empty seller ID is intentional.

The expected data includes "sellers": [{"id": ""}] with an empty string for the seller ID. If build_buyer_data retrieves the seller ID from context, this test doesn't set that context value, which may be why it's empty.

Consider either:

  1. Setting the seller ID in the context before calling build_buyer_data
  2. Adding a comment explaining why an empty seller ID is expected in this test scenario

20-28: LGTM!

The test correctly verifies that get_buyer fetches the buyer when an ID exists in context and stores it as a resource.

tests/seed/accounts/test_api_token.py (1)

24-32: LGTM!

The test correctly verifies that get_api_token fetches the token when an ID exists in context and stores it as a resource.

seed/accounts/module.py (1)

14-32: Module lookup and context caching look correct

get_module cleanly implements the “context first, then API” pattern and correctly caches back into the Context on a cache miss. Type check against Module plus ValueError handling from Context.get_resource are appropriate.

No changes needed here.

seed/accounts/seller.py (2)

14-32: get_seller caching logic is consistent and sound

The “id from context → resource from context → API fallback → cache back” pattern mirrors the other seed modules and uses Context.get_resource/set_resource correctly.

No changes needed here.


35-51: build_seller_data is straightforward and suitable for seeding

Generating a unique externalId via uuid.uuid4() when not provided, along with fixed address/currency data, is appropriate for deterministic seed data. Keeping external_id injectable is useful for tests and replayability.

Looks good as‑is.

tests/seed/accounts/test_seller.py (3)

20-29: get_seller happy‑path test is solid

This test correctly verifies:

  • The id is picked up from the context.
  • The async service’s get is used.
  • The retrieved Seller is cached back into the Context via get_resource.

No changes needed here.


36-53: Seller data expectations are clear and match the builder

test_build_seller_data defines the full expected payload and compares it directly to build_seller_data(external_id=external_id), which is concise and robust.

All good here.


83-88: seed_seller orchestration test is appropriate

Patching init_seller with an AsyncMock and asserting it is awaited once keeps the seed_seller test focused on orchestration behavior and logging side‑effects.

Looks good.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 85ab0a4 to 5542929 Compare December 3, 2025 03:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (3)
seed/accounts/seller.py (1)

60-60: Past issue addressed.

The init_seller function now correctly passes context and mpt_operations to get_seller, ensuring the caller's explicit choices are honored.

seed/accounts/user_group.py (1)

40-42: Past issue addressed.

The CLIENT_ACCOUNT_ID environment variable is now properly validated, raising a clear ValueError if missing.

seed/accounts/buyer.py (1)

41-43: Past issue addressed.

The CLIENT_ACCOUNT_ID environment variable is now properly validated with a clear error message.

🧹 Nitpick comments (11)
tests/seed/conftest.py (1)

29-31: Type hint inconsistency.

The return type annotation says AsyncMock but the fixture returns MagicMock(spec=AsyncMPTClient). This follows the same pattern as the existing vendor_client and operations_client fixtures, but the annotation is misleading.

Consider fixing the type hint for accuracy:

 @pytest.fixture
-def client_client() -> AsyncMock:
+def client_client() -> MagicMock:
     return MagicMock(spec=AsyncMPTClient)
seed/seed_api.py (1)

28-31: Consider re-raising exceptions after logging.

The current implementation catches all exceptions, logs them, and silently continues. This may mask failures during seeding, making it difficult to detect issues in CI/CD pipelines or automated runs.

If the seeding process should fail visibly when errors occur:

     try:
         await asyncio.gather(*tasks)
     except Exception:
         logger.exception("Exception occurred during seeding.")
+        raise
     finally:
         save_context(context, context_file)

Alternatively, if partial success is acceptable, consider returning a status or error summary.

tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused noqa directive.

The static analysis indicates that WPS210 is not recognized, making this directive ineffective. Either the WPS plugin is not enabled in the linting configuration, or the directive is unnecessary.

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:
seed/accounts/accounts.py (1)

13-13: Remove unused noqa directive.

The static analysis indicates that WPS217 is not recognized, making this directive ineffective.

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
tests/seed/accounts/test_module.py (2)

17-21: Chain exception properly in async iterator.

Per Python best practice (PEP 3134), exceptions raised in except blocks should be chained to preserve the traceback.

     async def __anext__(self):
         try:
             return next(self._iterator)
         except StopIteration:
-            raise StopAsyncIteration
+            raise StopAsyncIteration from None

34-36: Remove unused helper function.

The async_iter function is defined but never used in this test module.

-def async_iter(iter_items):
-    yield from iter_items
-
-
tests/seed/accounts/test_buyer.py (1)

36-60: Consider setting seller ID in context for complete payload validation.

The build_buyer_data test expects sellers: [{"id": ""}] because accounts.seller.id is not set in the context. While this validates the structure, consider adding a test case with the seller ID set to verify the complete payload.

tests/seed/catalog/test_product.py (1)

51-51: Consider using underscore prefix for unused lambda parameters.

The lambda ignores its parameters, which Ruff flags. While functional, using underscores signals intent.

-        patch("pathlib.Path.open", new=lambda *args, **kwargs: fake_file_factory()),
+        patch("pathlib.Path.open", new=lambda *_args, **_kwargs: fake_file_factory()),
tests/seed/accounts/test_user_group.py (1)

43-61: Consider testing the "existing user group" path.

Both test_init_user_group and test_init_user_group_create_new patch get_user_group to return None, testing the creation path. Consider modifying one test to return an existing user_group to verify the "already exists" branch where create() should NOT be called.

 async def test_init_user_group(
     context: Context, operations_client, user_groups_service, user_group
 ) -> None:
     user_groups_service.create.return_value = user_group
     operations_client.accounts.user_groups = user_groups_service
     context["accounts.account.id"] = "ACC-1086-6867"
     context["accounts.module.id"] = "MOD-456"
 
     with (
-        patch("seed.accounts.user_group.get_user_group", return_value=None),
-        patch(
-            "seed.accounts.user_group.build_user_group_data",
-            return_value=build_user_group_data(context),
-        ),
+        patch("seed.accounts.user_group.get_user_group", return_value=user_group),
     ):
         result = await init_user_group(context=context, mpt_operations=operations_client)
 
     assert result == user_group
-    user_groups_service.create.assert_called_once()
+    user_groups_service.create.assert_not_called()
seed/accounts/licensee.py (1)

92-97: Minor: Inconsistent log level for completion message.

seed_licensee uses logger.info for completion while seed_accounts (in accounts.py line 23) uses logger.debug. Consider aligning for consistency.

 async def seed_licensee() -> None:
     """Seed licensee."""
     logger.debug("Seeding licensee ...")
     await init_licensee()
-    logger.info("Seeding licensee completed.")
+    logger.debug("Seeding licensee completed.")
seed/accounts/module.py (1)

35-57: Docstring overstates behavior and list materialization is unnecessary

  • The docstring says “always fetch”, but refresh_module returns the existing module from get_module without re-fetching when it’s already present. Either update the docstring to match the current “fetch-on-miss” behavior or change the implementation to always refresh from the API.
  • When falling back to filter, you only ever use the first module but still build a full list. You could iterate until the first valid Module and return immediately to avoid extra work and allocations (use an async for loop and break / early return).
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85ab0a4 and 5542929.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (23)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • seed/container.py
  • tests/seed/accounts/test_api_token.py
  • tests/seed/test_seed_api.py
  • tests/seed/accounts/test_licensee.py
  • tests/seed/accounts/test_seller.py
  • tests/e2e/accounts/licensees/conftest.py
  • tests/e2e/accounts/buyers/conftest.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/conftest.py
  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (12)
seed/seed_api.py (3)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
seed/accounts/seller.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/api_tokens.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_api_token.py (1)
  • api_token (15-16)
tests/seed/accounts/test_user_group.py (3)
seed/accounts/user_group.py (4)
  • build_user_group_data (36-51)
  • get_user_group (15-32)
  • init_user_group (55-69)
  • seed_user_group (73-78)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/user_group.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/module.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/licensee.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_licensee.py (1)
  • licensee (12-30)
tests/seed/accounts/test_buyer.py (3)
seed/accounts/buyer.py (4)
  • build_buyer_data (39-61)
  • get_buyer (18-35)
  • init_buyer (65-85)
  • seed_buyer (89-93)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/buyer.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (93-97)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (73-77)
seed/accounts/user_group.py (1)
  • seed_user_group (73-78)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
tests/seed/catalog/test_product.py (3)
tests/seed/conftest.py (2)
  • context (15-16)
  • fake_file_factory (35-36)
seed/context.py (1)
  • Context (12-33)
seed/catalog/product.py (1)
  • init_product (45-65)
🪛 Ruff (0.14.7)
seed/accounts/user_group.py

42-42: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

87-87: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_module.py

21-21: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/seed/catalog/test_product.py

51-51: Unused lambda argument: args

(ARG005)


51-51: Unused lambda argument: kwargs

(ARG005)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (17)
tests/e2e/accounts/sellers/conftest.py (1)

20-20: LGTM!

Test data update aligns with the broader PR changes standardizing city values to "Los Angeles" across e2e seed fixtures.

seed/catalog/product.py (1)

51-65: LGTM!

The inverted control flow is cleaner - creating only when product is None and returning the existing product at the end. Error handling for creation failure is appropriately handled with clear logging.

seed/accounts/buyer.py (1)

76-81: Good: Proper type validation on API response.

Unlike seller.py and user_group.py, this correctly validates isinstance(created, Buyer) before using the result and handles errors appropriately. Consider applying this pattern to the other seed modules for consistency.

tests/seed/conftest.py (1)

34-36: LGTM!

The fake_file_factory fixture provides a clean way to mock file I/O in tests that need to patch Path.open.

seed/seed_api.py (1)

24-26: LGTM!

Running seed_catalog and seed_accounts concurrently is appropriate since they operate on independent domains. The task list with typed annotation and proper error handling with context persistence in finally ensures robustness.

tests/seed/accounts/test_accounts.py (1)

7-30: LGTM!

The test correctly verifies that seed_accounts() orchestrates all six seed functions. The approach of collecting mocks and iterating for assertions is clean and maintainable.

seed/accounts/accounts.py (1)

1-23: LGTM!

The sequential execution order is appropriate given the dependencies between entities (e.g., seed_licensee depends on seed_user_group resources being available in context). The orchestration is clean and well-logged.

tests/seed/accounts/test_module.py (1)

38-99: LGTM!

The tests comprehensively cover the module seeding functionality including retrieval, refresh, and seed orchestration paths.

tests/seed/accounts/test_buyer.py (1)

63-105: LGTM!

The tests properly verify both the "existing buyer" and "create new buyer" paths, including correct mock assertions for service calls.

tests/seed/catalog/test_product.py (1)

43-56: LGTM!

The test correctly uses the fake_file_factory fixture, properly invokes init_product with named arguments, and maintains the strict assert_called_once() assertion.

tests/seed/accounts/test_user_group.py (2)

64-78: Test relies on CLIENT_ACCOUNT_ID environment variable, not context.

The test sets context["accounts.account.id"] but build_user_group_data reads from os.getenv("CLIENT_ACCOUNT_ID") per the implementation snippet. This test may fail if the environment variable isn't set, or the assertion at line 69 may pass coincidentally if the env var happens to match.

Consider explicitly patching the environment variable to ensure test isolation:

+from unittest.mock import patch
+
 def test_build_user_group_data(context: Context) -> None:
-    context["accounts.account.id"] = "ACC-1086-6867"
     context["accounts.module.id"] = "MOD-456"
-    expected_data = {
-        ...
-        "account": {"id": "ACC-1086-6867"},
-        ...
-    }
-
-    result = build_user_group_data(context)
+    with patch.dict("os.environ", {"CLIENT_ACCOUNT_ID": "ACC-1086-6867"}):
+        expected_data = {
+            "name": "E2E Seeded User Group",
+            "account": {"id": "ACC-1086-6867"},
+            ...
+        }
+        result = build_user_group_data(context)
+        assert result == expected_data

98-106: LGTM!

The test_seed_user_group test correctly verifies that init_user_group is awaited during seeding.

seed/accounts/licensee.py (2)

38-66: LGTM!

The build_licensee_data function correctly retrieves dependencies from context and constructs a well-structured payload. The implicit requirement for accounts.user_group to exist is appropriate given the seeding order in seed_accounts.


17-35: LGTM!

The get_licensee function follows the established pattern for context-aware resource retrieval with API fallback.

seed/accounts/module.py (1)

14-32: Module retrieval and caching logic looks good

The get_module flow (context-first, then API, with type-checking and caching back into Context) is coherent and matches the Context.get_resource / set_resource contract. No changes needed here from a correctness perspective.

seed/accounts/api_tokens.py (2)

14-32: API token retrieval/caching logic is consistent

get_api_token correctly mirrors the module pattern: it looks up the id in context, tries the cached resource, then falls back to the API and updates context. This is clear and consistent.


70-75: Seeding wrapper is straightforward

seed_api_token is a thin async wrapper around init_api_token with debug logging, which is appropriate for the seeding entry point. No changes needed.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 5542929 to fcf3f67 Compare December 3, 2025 03:56
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
seed/accounts/module.py (1)

60-72: TRY003 lint warning already addressed in past review.

The Ruff TRY003 warning on line 69 was already flagged in a previous review. The suggestion to add # noqa: TRY003 or extract the message to a constant remains valid.

seed/accounts/seller.py (1)

55-74: Return type mismatch: function can return None but signature declares -> Seller.

Line 72 returns None on creation failure, but the signature at line 59 declares -> Seller. This causes mypy to flag unreachable code (hence the disable directive at line 1). For consistency with init_licensee which raises ValueError on failure, apply this fix:

 async def init_seller(
     context: Context = DEFAULT_CONTEXT,
     mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
 ) -> Seller:
     """Get or create seller."""
     seller = await get_seller(context=context, mpt_operations=mpt_operations)
     if seller is None:
         logger.debug("Creating seller ...")
         seller_data = build_seller_data()
         created = await mpt_operations.accounts.sellers.create(seller_data)
         if isinstance(created, Seller):
             context.set_resource("accounts.seller", created)
             context["accounts.seller.id"] = created.id
             logger.info("Seller created: %s", created.id)
             return created
         logger.warning("Seller creation failed")
-        return None
+        raise ValueError("Seller creation failed")
     logger.info("Seller already exists: %s", seller.id)
     return seller

After this fix, remove the # mypy: disable-error-code=unreachable directive at line 1.

seed/accounts/user_group.py (1)

55-74: Fix type inconsistency: function returns None but signature declares UserGroup.

The function signature on line 59 declares a return type of UserGroup, but line 72 returns None when creation fails. This violates the type contract and will cause type checking errors or runtime issues for callers expecting a UserGroup instance.

Apply this diff to raise an exception instead of returning None:

         if isinstance(created, UserGroup):
             context.set_resource("accounts.user_group", created)
             context["accounts.user_group.id"] = created.id
             logger.info("User group created: %s", created.id)
             return created
         logger.warning("User group creation failed")
-        return None
+        raise ValueError("User group creation failed")
     logger.info("User group already exists: %s", user_group.id)
     return user_group
🧹 Nitpick comments (6)
seed/accounts/module.py (1)

35-57: Docstring is inconsistent with implementation.

The docstring states "Refresh module in context (always fetch)" but the implementation returns early at line 57 if get_module() succeeds, skipping the API fetch. Consider updating the docstring to accurately reflect the "get or fetch" behavior:

-    """Refresh module in context (always fetch)."""
+    """Get module from context or fetch from API by name filter."""
tests/seed/accounts/test_seller.py (1)

56-80: Consider adding a test for the "seller exists" path.

Both test_init_seller and test_init_seller_create_new test the creation path (when get_seller returns None). The "seller already exists" path where get_seller returns an existing seller is not covered. Consider adding:

async def test_init_seller_exists(
    context: Context, operations_client, sellers_service, seller
) -> None:
    with patch("seed.accounts.seller.get_seller", return_value=seller):
        result = await init_seller(context=context, mpt_operations=operations_client)
    assert result == seller
    sellers_service.create.assert_not_called()
tests/seed/catalog/test_product.py (1)

51-51: Simplify the lambda to remove unused arguments.

The lambda accepts *args and **kwargs but doesn't use them. You can simplify by directly using fake_file_factory without the wrapper lambda.

Apply this diff:

-        patch("pathlib.Path.open", new=lambda *args, **kwargs: fake_file_factory()),
+        patch("pathlib.Path.open", new=fake_file_factory),
seed/accounts/accounts.py (1)

13-13: Remove unused noqa directive.

The # noqa: WPS217 directive is flagged as unused by static analysis. If the WPS217 rule isn't active in your linting configuration, remove the directive to keep the code clean.

Apply this diff:

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
tests/seed/accounts/test_module.py (1)

17-21: Add exception chaining for clarity.

When converting StopIteration to StopAsyncIteration, use raise ... from err to preserve the exception chain and distinguish it from errors in exception handling.

Apply this diff:

     async def __anext__(self):
         try:
             return next(self._iterator)
-        except StopIteration:
-            raise StopAsyncIteration
+        except StopIteration as err:
+            raise StopAsyncIteration from err
tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused noqa directive.

The # noqa: WPS210 directive is flagged as unused by static analysis. If the WPS210 rule isn't active in your linting configuration, remove the directive to keep the code clean.

Apply this diff:

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5542929 and fcf3f67.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • seed/accounts/api_tokens.py
  • tests/seed/conftest.py
  • tests/seed/accounts/test_user_group.py
  • tests/seed/accounts/test_licensee.py
  • seed/container.py
  • tests/e2e/accounts/licensees/conftest.py
  • tests/seed/accounts/test_api_token.py
  • tests/e2e/accounts/buyers/conftest.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-01T10:48:52.586Z
Learnt from: jentyk
Repo: softwareone-platform/mpt-api-python-client PR: 136
File: tests/e2e/notifications/subscribers/conftest.py:14-25
Timestamp: 2025-12-01T10:48:52.586Z
Learning: The repository softwareone-platform/mpt-api-python-client uses wemake-python-styleguide linter, so noqa directives for WPS rules should not be flagged as unused.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (9)
seed/seed_api.py (4)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
tests/seed/conftest.py (1)
  • context (15-16)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
seed/catalog/product.py (5)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
mpt_api_client/resources/catalog/products.py (1)
  • Product (49-50)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_seller.py (4)
mpt_api_client/resources/accounts/sellers.py (2)
  • AsyncSellersService (44-60)
  • Seller (13-14)
seed/accounts/seller.py (4)
  • build_seller_data (37-52)
  • get_seller (16-33)
  • init_seller (56-74)
  • seed_seller (78-82)
tests/seed/conftest.py (2)
  • context (15-16)
  • operations_client (25-26)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/seller.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/buyer.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (93-97)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (78-83)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
tests/seed/accounts/test_module.py (2)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
tests/seed/catalog/test_product.py (5)
tests/seed/conftest.py (3)
  • context (15-16)
  • vendor_client (20-21)
  • fake_file_factory (35-36)
seed/context.py (1)
  • Context (12-33)
tests/seed/catalog/test_item_group.py (1)
  • vendor_client (29-30)
tests/seed/catalog/test_product_parameters.py (1)
  • vendor_client (30-31)
seed/catalog/product.py (1)
  • init_product (45-65)
🪛 Ruff (0.14.7)
seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

87-87: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_module.py

21-21: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/seed/catalog/test_product.py

51-51: Unused lambda argument: args

(ARG005)


51-51: Unused lambda argument: kwargs

(ARG005)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (29)
tests/e2e/accounts/sellers/conftest.py (1)

19-23: City value change in seller fixture looks good

Switching the seeded seller address city to "Los Angeles" is a harmless test-data tweak and keeps the fixture structure intact. Just ensure there are no tests or downstream seed assertions still expecting the previous city value.

pyproject.toml (1)

116-134: LGTM!

The lint suppressions for WPS453 in seed/accounts/*.py and mpt_api_client/resources/accounts/*.py are appropriate for the new seeding modules being introduced. Based on learnings, this repository uses wemake-python-styleguide, so these per-file-ignores are consistent with existing patterns.

seed/accounts/module.py (1)

14-32: LGTM!

The get_module function correctly implements the context-first lookup pattern with API fallback and caching. The type checking with isinstance before returning is a good defensive practice.

seed/accounts/licensee.py (4)

17-35: LGTM!

The get_licensee function follows the established context-first lookup pattern with proper API fallback and caching.


38-66: LGTM with implicit dependency noted.

The function correctly builds the licensee payload. Note that line 46 assumes accounts.user_group is already in context, which implies the user group must be seeded before the licensee. Ensure the orchestration in seed_accounts maintains this ordering.


69-89: Good implementation with past feedback addressed.

The function now correctly forwards context and mpt_client to helper functions (lines 75, 77), addressing the concern from the previous review. The type validation on the API response at line 81 is also consistent with best practices seen in other seed modules.


92-97: LGTM!

Simple orchestration wrapper consistent with the pattern used in other seed modules.

seed/accounts/seller.py (3)

15-33: LGTM!

The get_seller function correctly implements the context-first lookup pattern with API fallback and caching, consistent with other seeding modules.


36-52: LGTM!

The UUID-based externalId generation ensures uniqueness as required.


77-82: LGTM!

Consistent orchestration pattern.

tests/seed/accounts/test_seller.py (4)

10-17: LGTM!

Fixtures are appropriately defined for testing seller seeding functionality.


20-33: LGTM!

Good coverage of the get_seller function: tests both the fetch path and the early-return-when-no-ID path.


36-53: LGTM!

Thorough validation of the build_seller_data output structure.


83-88: LGTM!

Appropriate test for the simple orchestration wrapper.

tests/seed/test_seed_api.py (1)

26-26: LGTM! Test coverage for accounts seeding added.

The test now correctly verifies that seed_accounts is invoked alongside seed_catalog during seed_api execution, aligning with the concurrent seeding implementation.

Also applies to: 38-38

seed/seed_api.py (1)

7-7: LGTM! Concurrent seeding implementation is well-structured.

The parallel execution of catalog and accounts seeding using asyncio.gather improves performance. The error handling with a finally block ensures context is always saved, maintaining data integrity even when seeding fails.

Also applies to: 20-20, 24-33

seed/catalog/product.py (1)

51-65: LGTM! Logic inversion improves code clarity.

The refactored control flow makes both paths explicit: product creation when None, and returning the existing product otherwise. This improves maintainability without changing behavior.

tests/seed/catalog/test_product.py (1)

44-44: LGTM! Test updates align with the new fixture pattern.

The test correctly uses the fake_file_factory fixture and keyword arguments for init_product. The strict assertion assert_called_once() appropriately verifies that create is called exactly once in the creation path.

Also applies to: 53-53, 56-56

seed/accounts/accounts.py (1)

13-23: LGTM! Account seeding orchestration is well-structured.

The function correctly orchestrates all account-related seed operations in sequence, with appropriate logging for debugging and observability.

tests/seed/accounts/test_buyer.py (1)

1-105: LGTM! Comprehensive test coverage for buyer seeding.

The test suite thoroughly covers all buyer seeding scenarios: retrieval with/without ID, data building, initialization with existing/new buyers, and orchestration. The tests use appropriate mocking, clear assertions, and follow pytest best practices.

tests/seed/accounts/test_module.py (1)

38-99: LGTM! Comprehensive test coverage for module seeding.

The test suite thoroughly covers module retrieval, refresh with filtering, and seed orchestration. The tests properly mock async iteration and verify context updates, aligning well with the module seeding implementation.

tests/seed/accounts/test_accounts.py (1)

6-30: LGTM! Orchestration test correctly verifies all seeders are called.

The test properly patches all six seed functions and verifies each is called exactly once, ensuring the seed_accounts orchestrator functions correctly. The structure is clean and follows pytest best practices.

seed/accounts/user_group.py (3)

15-33: LGTM!

The function correctly handles the absence of a user group ID, validates the type of cached resources, and falls back to the API when needed.


36-52: LGTM!

The function correctly validates the required environment variable and builds the user group data dictionary.


77-83: LGTM!

The orchestration logic is sound. Once init_user_group is fixed to raise an exception instead of returning None, this function will correctly propagate the created or existing UserGroup.

seed/accounts/buyer.py (4)

17-35: LGTM!

The function follows the established pattern for resource retrieval, properly handling context lookup, type validation, and API fallback.


38-61: LGTM!

The function correctly validates the required environment variable and constructs a complete buyer data dictionary with contact and address information.


64-85: Verify whether re-fetching the buyer after creation is necessary.

The function correctly handles creation and error cases. However, line 77 fetches the buyer again immediately after creation. If the create call already returns a complete Buyer instance, this extra API call may be unnecessary.

Please confirm whether this re-fetch is required to populate server-side fields or if the created instance is sufficient.


88-93: LGTM!

The orchestration function correctly delegates to init_buyer and provides appropriate logging. The return type of None is consistent with its role as a side-effect operation.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from fcf3f67 to 6772c50 Compare December 3, 2025 04:03
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
seed/catalog/product.py (1)

18-41: Forward injected context into get_product; note that publish_product cannot forward its mpt_operations client

init_product, review_product, and publish_product all accept context and a client parameter, but they call get_product() with no arguments. This breaks dependency injection for tests or alternate wiring. However, there's an important constraint: get_product expects mpt_vendor (for catalog read operations), while publish_product receives mpt_operations (for operations). These are intentionally different clients.

The correct fix is:

 async def init_product(
     context: Context = DEFAULT_CONTEXT,
     mpt_vendor: AsyncMPTClient = DEFAULT_MPT_VENDOR,
 ) -> Product:
     """Get or create product."""
-    product = await get_product()
+    product = await get_product(context=context, mpt_vendor=mpt_vendor)
@@
 async def review_product(
     context: Context = DEFAULT_CONTEXT,
     mpt_vendor: AsyncMPTClient = DEFAULT_MPT_VENDOR,
 ) -> Product | None:
@@
-    product = await get_product()
+    product = await get_product(context=context, mpt_vendor=mpt_vendor)
@@
 async def publish_product(
     context: Context = DEFAULT_CONTEXT,
     mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
 ) -> Product | None:
@@
-    product = await get_product()
+    product = await get_product(context=context)

For publish_product, only forward context since get_product uses mpt_vendor for fetching, not mpt_operations.

Ruff also flags ValueError("Product creation failed") (TRY003) at line 63. Either extract the message to a constant or add # noqa: TRY003.

♻️ Duplicate comments (2)
seed/accounts/module.py (1)

35-57: Clarify refresh semantics and address the TRY003 warning (repeat of earlier feedback)

refresh_module’s docstring promises “always fetch”, but the implementation returns the cached module when present and only hits the API when get_module returns None. Either adjust the docstring to reflect the actual behavior (“fetch if missing”) or change the code to always re-fetch when called.

Ruff’s TRY003 warning on ValueError("Could not seed module: no valid Module found.") is still present. As suggested earlier, either move the message into a constant and raise ValueError(MODULE_SEED_ERROR) or keep the literal and append # noqa: TRY003 on that line.

Also applies to: 60-71

seed/accounts/user_group.py (1)

55-74: Fix type safety violation: return type mismatch.

init_user_group declares return type UserGroup but returns None on line 72 when creation fails. This violates the type contract and will cause runtime type errors when seed_user_group (line 81) receives None.

For consistency with seed/accounts/buyer.py (lines 76-83), raise an exception instead of returning None.

Apply this diff:

         created = await mpt_operations.accounts.user_groups.create(user_group_data)
         if isinstance(created, UserGroup):
             context.set_resource("accounts.user_group", created)
             context["accounts.user_group.id"] = created.id
             logger.info("User group created: %s", created.id)
             return created
         logger.warning("User group creation failed")
-        return None
+        raise ValueError("User group creation failed")
     logger.info("User group already exists: %s", user_group.id)
     return user_group
🧹 Nitpick comments (3)
seed/accounts/licensee.py (1)

38-66: Tighten env var handling and satisfy Ruff TRY003 on the error message

build_licensee_data pulls CLIENT_ACCOUNT_ID from the environment and passes it straight into the payload; if the env var is missing, you’ll send {"account": {"id": None}}, which will likely fail deeper in the stack. It’s safer to validate early (e.g., raise or log clearly when the env var is absent) rather than relying on downstream errors.

Ruff also flags the literal message in ValueError("Licensee creation failed") (TRY003). You can either move this string into a module-level constant and reuse it in the raise, or, if you prefer the literal, append # noqa: TRY003 on that line.

Also applies to: 69-87

tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused noqa directive.

The WPS210 rule is not recognized by Ruff (used in this project). As per the static analysis hint, this directive can be removed.

Apply this diff:

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:
tests/seed/accounts/test_user_group.py (1)

48-48: Remove unused context assignment.

Line 48 sets context["accounts.account.id"] but build_user_group_data is patched in this test, so the value is never used. This can be removed for clarity.

Apply this diff:

     user_groups_service.create.return_value = user_group
     operations_client.accounts.user_groups = user_groups_service
-    context["accounts.account.id"] = "ACC-1086-6867"
     context["accounts.module.id"] = "MOD-456"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcf3f67 and 6772c50.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • tests/e2e/accounts/sellers/conftest.py
  • seed/accounts/api_tokens.py
  • seed/seed_api.py
  • tests/seed/test_seed_api.py
  • seed/accounts/seller.py
  • pyproject.toml
  • tests/seed/accounts/test_licensee.py
  • tests/seed/accounts/test_buyer.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/conftest.py
  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (12)
seed/accounts/licensee.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_module.py (4)
mpt_api_client/resources/accounts/modules.py (2)
  • AsyncModulesService (29-32)
  • Module (11-12)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/user_group.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_seller.py (2)
seed/accounts/seller.py (4)
  • build_seller_data (37-52)
  • get_seller (16-33)
  • init_seller (56-74)
  • seed_seller (78-82)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/module.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_user_group.py (2)
seed/accounts/user_group.py (4)
  • build_user_group_data (37-52)
  • get_user_group (16-33)
  • init_user_group (56-74)
  • seed_user_group (78-83)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
tests/seed/conftest.py (1)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
tests/seed/accounts/test_api_token.py (2)
seed/accounts/api_tokens.py (4)
  • build_api_token_data (36-48)
  • get_api_token (15-32)
  • init_api_token (52-67)
  • seed_api_token (71-75)
seed/context.py (1)
  • get_resource (19-29)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (93-97)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (78-83)
seed/accounts/buyer.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/catalog/product.py (5)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
mpt_api_client/http/mixins.py (6)
  • create (18-26)
  • create (86-112)
  • create (118-144)
  • create (190-216)
  • create (262-270)
  • create (331-358)
mpt_api_client/resources/catalog/products.py (1)
  • Product (49-50)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
🪛 Ruff (0.14.7)
seed/accounts/licensee.py

87-87: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_module.py

21-21: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

tests/seed/catalog/test_product.py

51-51: Unused lambda argument: args

(ARG005)


51-51: Unused lambda argument: kwargs

(ARG005)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (8)
tests/e2e/accounts/licensees/conftest.py (1)

35-35: LGTM! Test data update aligns with PR objectives.

The address line change to "123 Main St" is a straightforward test data update that aligns with the PR's goal of adding seeded accounts e2e data.

tests/e2e/accounts/buyers/conftest.py (1)

30-36: City test data update looks good

Changing the buyer address city to "Los Angeles" is a straightforward fixture data adjustment and aligns with the rest of the seeded account data.

seed/container.py (1)

45-68: Accounts seeding modules correctly added to DI wiring

The additional seed.accounts.* modules wired here follow the same pattern as the existing catalog wiring and should allow dependency-injected seeding for all account-related resources.

seed/accounts/accounts.py (1)

13-23: Remove the unused noqa: WPS217 directive

Ruff reports # noqa: WPS217 on seed_accounts as an unused/unknown directive (RUF100). The function body is simple enough that this suppression isn’t needed; you can safely drop the # noqa: WPS217 comment from the signature.

⛔ Skipped due to learnings
Learnt from: jentyk
Repo: softwareone-platform/mpt-api-python-client PR: 136
File: tests/e2e/notifications/subscribers/conftest.py:14-25
Timestamp: 2025-12-01T10:48:52.586Z
Learning: The repository softwareone-platform/mpt-api-python-client uses wemake-python-styleguide linter, so noqa directives for WPS rules should not be flagged as unused.
tests/seed/conftest.py (1)

10-36: LGTM!

The new test fixtures and helper function follow existing patterns and provide useful test utilities for file I/O mocking and client instantiation.

tests/seed/accounts/test_api_token.py (1)

57-70: LGTM!

Good use of monkeypatch.setenv to properly mock the environment variable, addressing the previous review feedback.

seed/accounts/buyer.py (1)

17-93: LGTM!

The implementation follows established patterns, properly forwards injected dependencies, validates required environment variables, and includes type checking for API responses. Past review feedback has been addressed.

tests/seed/accounts/test_seller.py (1)

1-88: LGTM!

Test coverage is comprehensive, covering retrieval, data building, initialization (both with and without mocked data builders), and seeding orchestration.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 6772c50 to e3db949 Compare December 3, 2025 04:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
tests/seed/catalog/test_product.py (1)

62-103: Patch async get_product with AsyncMock in remaining tests.

Four tests still patch the async function get_product with a regular mock instead of AsyncMock:

  • test_review_product_draft_status (line 69)
  • test_review_product_non_draft_status (line 79)
  • test_publish_product_reviewing_status (line 89)
  • test_publish_product_non_reviewing_status (line 99)

This causes TypeError when the test code awaits the patched function.

Apply this pattern to all four tests:

 async def test_review_product_draft_status(
     context, products_service, vendor_client, product
 ) -> None:
     product.status = "Draft"
     products_service.review.return_value = product
     vendor_client.catalog.products = products_service
-    with (
-        patch("seed.catalog.product.get_product", return_value=product),
-    ):
+    with patch("seed.catalog.product.get_product", new_callable=AsyncMock) as mock_get_product:
+        mock_get_product.return_value = product
         result = await review_product(context, mpt_vendor=vendor_client)
 
         assert result == product
         products_service.review.assert_called_once()

Apply the same pattern to the other three tests at lines 79, 89, and 99.

seed/accounts/seller.py (1)

1-1: Drop obsolete mypy: disable-error-code=unreachable

init_seller is now correctly typed as Seller | None, and the control flow matches that signature, so the unreachable suppression at module level is no longer justified. Removing it will let mypy surface any future real issues.

🧹 Nitpick comments (3)
seed/accounts/accounts.py (1)

13-23: Consider removing the unused noqa directive.

The sequential seeding logic is correct. However, Ruff reports that the # noqa: WPS217 directive on line 13 is unused (unknown code). Consider removing it.

Apply this diff:

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
     """Seed accounts data including account."""
tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused # noqa: WPS210 directive

Ruff reports this as an unknown/unused noqa code. Since the function body is simple and no longer needs a complexity suppression, you can safely drop the directive to keep lint output clean.

seed/accounts/user_group.py (1)

41-43: TRY003: consider extracting the env‑var error message

Ruff’s TRY003 warning here is stylistic, but if you want to satisfy it you can move the message into a constant (or a small custom exception), e.g.:

-    account_id = os.getenv("CLIENT_ACCOUNT_ID")
-    if not account_id:
-        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
+    account_id = os.getenv("CLIENT_ACCOUNT_ID")
+    if not account_id:
+        raise ValueError(CLIENT_ACCOUNT_ID_MISSING_MSG)

with CLIENT_ACCOUNT_ID_MISSING_MSG defined at module level. Functionally it’s fine as‑is, so this is optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6772c50 and e3db949.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • seed/container.py
  • seed/accounts/api_tokens.py
  • tests/seed/accounts/test_api_token.py
  • tests/seed/accounts/test_licensee.py
  • tests/seed/accounts/test_user_group.py
  • tests/seed/accounts/test_buyer.py
  • tests/e2e/accounts/licensees/conftest.py
  • pyproject.toml
  • tests/seed/conftest.py
  • tests/e2e/accounts/buyers/conftest.py
🧰 Additional context used
🧬 Code graph analysis (7)
seed/accounts/buyer.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_module.py (2)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/module.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/seller.py (8)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/e2e/conftest.py (1)
  • logger (63-64)
tests/e2e/accounts/conftest.py (1)
  • seller_id (27-28)
tests/seed/accounts/test_seller.py (1)
  • seller (11-12)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/licensee.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_licensee.py (1)
  • licensee (12-30)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/accounts/user_group.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
🪛 Ruff (0.14.7)
seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

87-87: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (23)
seed/catalog/product.py (1)

51-65: LGTM! Control flow restructuring is clear.

The refactored control flow—guarding product creation behind if product is None: and explicitly returning the existing product at the end—is functionally equivalent to the previous early-return pattern and improves readability.

tests/e2e/accounts/sellers/conftest.py (1)

20-20: LGTM!

The test data update aligns the city value with the seeded account data used elsewhere in the PR (e.g., buyer and licensee addresses also use "Los Angeles").

seed/accounts/buyer.py (4)

17-35: LGTM!

The function correctly retrieves or fetches the buyer, with proper context caching and resource management.


38-61: LGTM!

The environment variable validation added in line 42-43 ensures clear failure messages when CLIENT_ACCOUNT_ID is missing.


64-85: LGTM!

The function correctly forwards injected parameters to helper functions and handles both existing and new buyer creation paths.


88-92: LGTM!

The orchestration function provides appropriate logging around the buyer initialization.

seed/accounts/licensee.py (3)

17-35: LGTM!

The function correctly retrieves or fetches the licensee, with proper context caching and resource management.


69-89: LGTM!

The function correctly forwards injected parameters to helper functions and handles both existing and new licensee creation paths.


92-97: LGTM!

The orchestration function provides appropriate logging around the licensee initialization.

seed/accounts/module.py (3)

14-32: LGTM!

The function correctly retrieves the module from context or fetches from the API with proper caching.


35-57: LGTM!

The function appropriately fetches a new module by filtering for "Access Management" and handles the async iteration pattern correctly.


60-72: LGTM!

The orchestration correctly delegates to get_module and falls back to refresh_module when needed.

tests/seed/test_seed_api.py (2)

26-26: LGTM!

The test now patches seed_accounts to verify it's invoked by the updated seed_api function.


38-38: LGTM!

The assertion verifies that seed_accounts is called once during the seeding process.

seed/seed_api.py (2)

7-7: LGTM!

The import enables the new accounts seeding orchestration.


20-26: LGTM!

Concurrent execution of catalog and accounts seeding via asyncio.gather is an appropriate pattern for independent seeding tasks.

seed/accounts/accounts.py (1)

1-8: LGTM!

The imports correctly reference all account seeding functions.

tests/seed/accounts/test_module.py (5)

10-21: LGTM!

The exception chaining on line 21 correctly addresses the B904 linting rule by propagating the StopIteration context.


38-46: LGTM!

The test correctly verifies module retrieval and context storage.


49-51: LGTM!

The test correctly verifies the behavior when no module ID is present.


54-70: LGTM!

The test correctly uses AsyncMock for the async get_module function and verifies the refresh flow.


73-89: LGTM!

The test correctly uses AsyncMock and validates module refresh when a new module is found.

tests/seed/accounts/test_seller.py (1)

10-88: Seller seeding tests provide solid coverage

The tests exercise the main retrieval, creation, and seeding paths (including context caching and DI usage) and look consistent with the current seller.py implementation. No issues spotted.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from e3db949 to d6a9feb Compare December 3, 2025 04:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
tests/seed/catalog/test_product.py (1)

48-53: Remove duplicate pathlib.Path.open patch.

Lines 51 and 52 both patch pathlib.Path.open. The second patch overrides the first, making line 51 effectively dead code. This was flagged in a previous review but the duplicate remains.

Apply this diff to remove the duplicate:

     with (
         patch("seed.catalog.product.get_product", new_callable=AsyncMock) as mock_get_product,
         patch("seed.catalog.product.icon", new=MagicMock()),
-        patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
         patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
     ):
seed/accounts/module.py (1)

68-69: Address Ruff TRY003 on the ValueError message.

This issue was previously flagged. To satisfy the linter while keeping the descriptive message, either add # noqa: TRY003 or extract the message into a module-level constant.

-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError("Could not seed module: no valid Module found.")  # noqa: TRY003
🧹 Nitpick comments (6)
seed/accounts/user_group.py (1)

1-1: Remove obsolete mypy suppression.

All functions in this file correctly annotate their return types as UserGroup | None where applicable, and there are no unreachable code paths. The # mypy: disable-error-code=unreachable suppression on line 1 is no longer needed and should be removed.

-# mypy: disable-error-code=unreachable
 import logging
seed/accounts/accounts.py (1)

13-13: Remove unused noqa directive.

Ruff flagged # noqa: WPS217 as unused since WPS217 is not a recognized Ruff rule code.

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
tests/seed/accounts/test_buyer.py (1)

81-97: Consider asserting context updates after buyer creation.

The test verifies create is called but doesn't assert that context.set_resource and context["accounts.buyer.id"] are updated after creation. This would ensure the full creation flow including context caching is validated.

tests/seed/accounts/test_accounts.py (2)

6-6: Remove unused noqa directive.

Ruff flagged # noqa: WPS210 as unused since WPS210 is not a recognized Ruff rule code.

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:

29-30: Consider using assert_awaited_once() for async mocks.

Since these are AsyncMock instances, using assert_awaited_once() is more semantically correct than assert_called_once() as it specifically verifies the coroutine was awaited.

         for mock in mocks:
-            mock.assert_called_once()
+            mock.assert_awaited_once()
seed/accounts/licensee.py (1)

26-35: Consider simplifying the conditional logic.

After the try/except block, licensee is either a valid Licensee or None. The isinstance check on line 30 is effectively equivalent to if licensee is None. Consider simplifying for clarity:

     try:
         licensee = context.get_resource("accounts.licensee", licensee_id)
     except ValueError:
         licensee = None
-    if not isinstance(licensee, Licensee):
+    if licensee is None:
         licensee = await mpt_client.accounts.licensees.get(licensee_id)
         context.set_resource("accounts.licensee", licensee)
         context["accounts.licensee.id"] = licensee.id
-        return licensee
-    return licensee
+    return licensee
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3db949 and d6a9feb.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • pyproject.toml
  • tests/seed/accounts/test_module.py
  • tests/e2e/accounts/buyers/conftest.py
  • seed/accounts/api_tokens.py
  • tests/seed/accounts/test_api_token.py
  • seed/accounts/seller.py
  • tests/e2e/accounts/sellers/conftest.py
  • tests/seed/conftest.py
  • seed/container.py
  • tests/seed/accounts/test_seller.py
  • tests/seed/accounts/test_licensee.py
  • tests/e2e/accounts/licensees/conftest.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (10)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
tests/seed/accounts/test_buyer.py (3)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/accounts/buyer.py (4)
  • build_buyer_data (39-61)
  • get_buyer (18-35)
  • init_buyer (65-85)
  • seed_buyer (89-93)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/module.py (5)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/licensee.py (3)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/buyer.py (2)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (3)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/seed_api.py (3)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
tests/seed/catalog/test_product.py (2)
tests/seed/conftest.py (2)
  • context (15-16)
  • fake_file_factory (35-36)
seed/catalog/product.py (1)
  • init_product (45-65)
seed/catalog/product.py (7)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
tests/e2e/conftest.py (1)
  • logger (63-64)
mpt_api_client/http/mixins.py (6)
  • create (18-26)
  • create (86-112)
  • create (118-144)
  • create (190-216)
  • create (262-270)
  • create (331-358)
mpt_api_client/resources/catalog/products.py (1)
  • Product (49-50)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_user_group.py (2)
seed/accounts/user_group.py (4)
  • build_user_group_data (37-52)
  • get_user_group (16-33)
  • init_user_group (56-76)
  • seed_user_group (80-85)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (95-99)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
🪛 Ruff (0.14.7)
tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

45-45: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (16)
tests/seed/test_seed_api.py (1)

26-38: LGTM!

The test correctly mocks the new seed_accounts function and asserts it's called once during the seed_api execution, maintaining consistency with the existing seed_catalog assertion pattern.

seed/catalog/product.py (1)

51-65: LGTM!

The refactored init_product correctly guards product creation behind a None check. The control flow is clear: create and return when missing, log and return when found. Error handling with the isinstance check and ValueError on failure is consistent with other seed modules.

seed/accounts/buyer.py (1)

64-85: LGTM!

The init_buyer function correctly passes injected parameters to get_buyer and build_buyer_data, addressing previous review feedback. The create-then-fetch pattern ensures complete buyer data is cached.

seed/accounts/user_group.py (1)

55-76: LGTM!

The init_user_group function correctly validates the API response type after creation and handles both success and failure paths appropriately. The early return pattern for existing user groups improves readability.

seed/seed_api.py (2)

20-20: LGTM!

The type annotation list[asyncio.Task[object]] correctly captures that the task list can contain tasks with heterogeneous return types.


24-29: Verify concurrent execution safety between catalog and accounts seeding.

The seed_catalog() and seed_accounts() tasks now run concurrently via asyncio.gather. Ensure there are no shared dependencies between catalog entities (products, item groups, parameters) and account entities (seller, buyer, module, API token, user group, licensee) that could cause race conditions or ordering issues.

seed/accounts/accounts.py (1)

16-21: Sequential execution is appropriate for dependent entities.

The sequential ordering (seller → buyer → module → api_token → user_group → licensee) correctly handles dependencies between account entities. Based on the relevant code snippets, build_buyer_data references seller_id from context, confirming this ordering is intentional.

tests/seed/accounts/test_buyer.py (1)

100-104: LGTM!

The test correctly verifies that seed_buyer delegates to init_buyer using assert_awaited_once().

tests/seed/accounts/test_user_group.py (2)

43-61: LGTM!

The test correctly sets up context with required module ID, patches dependencies, and verifies the creation flow through init_user_group.


64-78: LGTM!

The test correctly uses monkeypatch.setenv to set CLIENT_ACCOUNT_ID, addressing the previous review feedback about environment variable requirements.

seed/accounts/module.py (2)

14-32: LGTM!

The get_module function correctly implements the context-first lookup pattern with API fallback, properly handling the ValueError from context.get_resource when the resource doesn't exist.


35-57: LGTM!

The refresh_module function correctly forwards dependencies to get_module and handles the async iteration over filtered modules appropriately. The instance check before accessing first_module.id is a good defensive practice.

seed/accounts/licensee.py (4)

1-14: LGTM!

The imports and module setup are appropriate. The global icon path cleanly references the logo asset relative to the module's location.


38-68: LGTM - validation added as requested.

The CLIENT_ACCOUNT_ID validation has been properly implemented with a clear error message. The TRY003 static analysis hint is a minor style preference and acceptable for this use case.


71-91: LGTM - dependency forwarding addressed.

The context and mpt_client parameters are now properly forwarded to helper functions as previously requested.

Minor style note: Line 81 uses pathlib.Path.open(icon, "rb") which works, but icon.open("rb") would be more idiomatic since icon is already a Path instance.


94-99: LGTM!

The seeding orchestrator is clean and follows the established pattern used by other seed modules.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from d6a9feb to 6edaaa8 Compare December 3, 2025 13:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (7)
tests/seed/catalog/test_product.py (1)

44-55: Remove duplicate pathlib.Path.open patch in test_get_or_create_product_create_new.

Lines 51–52 patch pathlib.Path.open twice with the same lambda; the second patch simply overrides the first, so the first is redundant noise.

You can collapse this to a single patch:

     with (
         patch("seed.catalog.product.get_product", new_callable=AsyncMock) as mock_get_product,
         patch("seed.catalog.product.icon", new=MagicMock()),
-        patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
-        patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
+        patch("pathlib.Path.open", new=lambda *_, **__: fake_file_factory()),
     ):

This keeps the behavior the same while simplifying the test and avoiding double-patching the same target.

tests/seed/accounts/test_buyer.py (2)

36-60: Test will fail: missing CLIENT_ACCOUNT_ID environment variable and seller ID.

Per seed/accounts/buyer.py lines 38-42, build_buyer_data reads CLIENT_ACCOUNT_ID from os.getenv(), not from context. The test sets context["accounts.account.id"] but never sets the environment variable, so the function will raise ValueError("CLIENT_ACCOUNT_ID environment variable is required"). Additionally, line 55 expects an empty seller ID, but the actual function requires context["accounts.seller.id"].

Apply this diff to fix:

-def test_build_buyer_data(context: Context) -> None:
-    context["accounts.account.id"] = "ACC-1086-6867"
+def test_build_buyer_data(context: Context, monkeypatch) -> None:
+    monkeypatch.setenv("CLIENT_ACCOUNT_ID", "ACC-1086-6867")
+    context["accounts.seller.id"] = "SEL-123"
     expected_data = {
         "name": "E2E Seeded Buyer",
         "account": {
             "id": "ACC-1086-6867",
         },
+        "sellers": [{"id": "SEL-123"}],
         "contact": {
             "firstName": "first",
             "lastName": "last",
             "email": "created.buyer@example.com",
         },
         "address": {
             "addressLine1": "123 Main St",
             "city": "Los Angeles",
             "state": "CA",
             "postCode": "12345",
             "country": "US",
         },
-        "sellers": [{"id": ""}],
     }

81-98: Test setup will fail: missing CLIENT_ACCOUNT_ID environment variable.

Line 92 evaluates build_buyer_data(context) during patch setup. Since context["accounts.account.id"] is set but the CLIENT_ACCOUNT_ID environment variable is not, the function will raise ValueError before the test runs.

Apply this diff to fix:

 async def test_init_buyer_create_new(
     context: Context, operations_client, buyers_service, buyer
 ) -> None:
     buyers_service.create.return_value = buyer
     operations_client.accounts.buyers = buyers_service
 
-    context["accounts.account.id"] = "ACC-1086-6867"
+    monkeypatch.setenv("CLIENT_ACCOUNT_ID", "ACC-1086-6867")
+    context["accounts.seller.id"] = "SEL-123"
     buyers_service.get.return_value = buyer
     with (
         patch("seed.accounts.buyer.get_buyer", return_value=None),
         patch("pathlib.Path.open", return_value=AsyncMock()),
         patch("seed.accounts.buyer.build_buyer_data", return_value=build_buyer_data(context)),
     ):
         result = await init_buyer(context=context, mpt_operations=operations_client)

Also add monkeypatch parameter:

-async def test_init_buyer_create_new(
-    context: Context, operations_client, buyers_service, buyer
+async def test_init_buyer_create_new(
+    context: Context, operations_client, buyers_service, buyer, monkeypatch
 ) -> None:
seed/accounts/seller.py (1)

1-1: Remove the mypy error suppression directive.

The # mypy: disable-error-code=unreachable at line 1 appears unnecessary. All code paths in the file are reachable:

  • get_seller() has three reachable return statements
  • init_seller() returns either created or None or seller depending on conditionals
  • No control flow dead ends exist

The function signatures correctly reflect return types (Seller | None), and past improvements to type validation have been applied. Remove the directive:

-# mypy: disable-error-code=unreachable
 import logging
seed/accounts/buyer.py (1)

30-34: Add type validation after API fetch.

When fetching buyer from the API on line 31, the result is stored in context without verifying it's a valid Buyer instance. If the API returns an error or unexpected type, this could corrupt the context.

Apply this diff:

     if not isinstance(buyer, Buyer):
         buyer = await mpt_operations.accounts.buyers.get(buyer_id)
+        if not isinstance(buyer, Buyer):
+            return None
         context.set_resource("accounts.buyer", buyer)
         context["accounts.buyer.id"] = buyer.id
         return buyer
seed/accounts/api_tokens.py (1)

40-48: Validate CLIENT_ACCOUNT_ID and module_id before building payload.

Both account_id and module_id can be None or empty, leading to confusing API errors.

Apply this diff:

     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     module_id = context.get_string("accounts.module.id")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is not set.")
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first.")
     return {
         "account": {"id": account_id},
seed/accounts/user_group.py (1)

28-32: Add type validation after API fetch.

Same issue as in buyer.py: when fetching from API on line 29, the result is stored in context without verifying it's a valid UserGroup instance.

Apply this diff:

     if not isinstance(user_group, UserGroup):
         user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
+        if not isinstance(user_group, UserGroup):
+            return None
         context.set_resource("accounts.user_group", user_group)
         context["accounts.user_group.id"] = user_group.id
         return user_group
🧹 Nitpick comments (10)
seed/catalog/product.py (1)

50-65: Thread context/mpt_vendor into get_product for consistency.

init_product accepts context and mpt_vendor but calls get_product() with no arguments, so the existence check uses DI-injected defaults while the creation path uses the explicit arguments. If a caller passes a non-default Context or vendor client, you could end up checking one context and writing to another.

Refactor to route the same dependencies through:

-    product = await get_product()
+    product = await get_product(context=context, mpt_vendor=mpt_vendor)

This keeps retrieval and creation aligned and makes the behavior clearer and more testable.

seed/accounts/accounts.py (1)

13-13: Remove unused noqa directive.

Ruff reports that WPS217 is not a recognized error code. This noqa directive appears to be for the wemake-python-styleguide linter, which may not be active in this project.

Apply this diff:

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
     """Seed accounts data including account."""

Based on static analysis hints.

tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused noqa directive.

Ruff reports that WPS210 is not a recognized error code. This noqa directive appears to be for the wemake-python-styleguide linter, which may not be active in this project.

Apply this diff:

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:
     with (

Based on static analysis hints.

seed/accounts/module.py (1)

69-69: Consider addressing Ruff TRY003 for the error message.

Ruff suggests avoiding long messages directly in exception raises. You can either extract the message to a constant or add a noqa comment if the explicit message is important.

Option 1 - Add suppression:

-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError("Could not seed module: no valid Module found.")  # noqa: TRY003

Option 2 - Extract to constant:

+MODULE_SEED_ERROR = "Could not seed module: no valid Module found."
+
 @inject
 async def seed_module() -> Module:
     """Seed module."""
     logger.debug("Seeding module ...")
     existing_module = await get_module()
     if existing_module is None:
         refreshed = await refresh_module()
         logger.debug("Seeding module completed.")
         if refreshed is None:
-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError(MODULE_SEED_ERROR)

Based on static analysis hints.

seed/accounts/licensee.py (1)

45-45: Consider addressing Ruff TRY003 for error messages.

Ruff suggests avoiding long messages directly in exception raises (lines 45 and 89). You can either add noqa comments or extract messages to constants.

Apply this diff:

-        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")  # noqa: TRY003
-        raise ValueError("Licensee creation failed")
+        raise ValueError("Licensee creation failed")  # noqa: TRY003

Based on static analysis hints.

Also applies to: 89-89

seed/accounts/buyer.py (1)

44-48: Validate seller_id before building payload.

seller_id is retrieved from context with an empty string default. If accounts.seller.id is missing, the payload will contain an empty seller ID, potentially causing an unclear API error.

Apply this diff:

     seller_id = context.get_string("accounts.seller.id")
+    if not seller_id:
+        raise ValueError("accounts.seller.id missing from context; seed seller first.")
     return {
         "name": "E2E Seeded Buyer",
seed/accounts/api_tokens.py (2)

27-31: Add type validation after API fetch.

Same issue as in buyer.py: when fetching from API on line 28, the result is stored in context without verifying it's a valid ApiToken instance.

Apply this diff:

     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
+        if not isinstance(api_token, ApiToken):
+            return None
         context.set_resource("accounts.api_token", api_token)
         context["accounts.api_token.id"] = api_token.id
         return api_token

58-67: Add type validation on API response.

Unlike init_buyer, this function doesn't validate the create() response. For consistency and safety, verify the result is an ApiToken instance.

Apply this diff:

     if api_token is None:
         logger.debug("Creating API token ...")
         api_token_data = build_api_token_data(context=context)
-        api_token = await mpt_ops.accounts.api_tokens.create(api_token_data)
-        context.set_resource("accounts.api_token", api_token)
-        context["accounts.api_token.id"] = api_token.id
-        logger.info("API token created: %s", api_token.id)
+        created = await mpt_ops.accounts.api_tokens.create(api_token_data)
+        if isinstance(created, ApiToken):
+            context.set_resource("accounts.api_token", created)
+            context["accounts.api_token.id"] = created.id
+            logger.info("API token created: %s", created.id)
+            return created
+        logger.warning("API token creation failed")
+        raise ValueError("API token creation failed")
     else:
         logger.info("API token found: %s", api_token.id)
     return api_token
seed/accounts/user_group.py (2)

44-51: Validate module_id before building payload.

module_id could be an empty string if accounts.module.id is missing from context, leading to an unclear API error.

Apply this diff:

     module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first.")
     return {
         "name": "E2E Seeded User Group",

1-1: Remove mypy suppression if no longer needed.

The return types for init_user_group and seed_user_group now honestly include None. Verify if this suppression is still required, and if not, remove it to enable proper type checking.

Run the following script to check if removing the suppression causes mypy errors:

#!/bin/bash
# Remove suppression and run mypy on this file
sed '1d' seed/accounts/user_group.py > /tmp/user_group_test.py
echo "Testing mypy without suppression..."
pip install mypy -q 2>/dev/null
mypy --ignore-missing-imports /tmp/user_group_test.py 2>&1 || echo "Mypy check complete"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6a9feb and 6edaaa8.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • pyproject.toml
  • tests/seed/accounts/test_module.py
  • tests/seed/accounts/test_seller.py
  • seed/container.py
  • tests/seed/conftest.py
  • tests/e2e/accounts/licensees/conftest.py
  • tests/e2e/accounts/buyers/conftest.py
  • tests/seed/test_seed_api.py
  • tests/seed/accounts/test_api_token.py
  • seed/seed_api.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (13)
seed/accounts/licensee.py (5)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
tests/seed/conftest.py (1)
  • context (15-16)
tests/e2e/accounts/licensees/conftest.py (1)
  • licensee_id (15-16)
tests/e2e/accounts/conftest.py (2)
  • account_id (30-31)
  • seller_id (35-36)
tests/seed/catalog/test_product.py (2)
tests/seed/conftest.py (2)
  • context (15-16)
  • fake_file_factory (35-36)
seed/catalog/product.py (1)
  • init_product (45-65)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/accounts/api_tokens.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_licensee.py (3)
mpt_api_client/resources/accounts/licensees.py (2)
  • AsyncLicenseesService (48-58)
  • Licensee (21-22)
seed/accounts/licensee.py (4)
  • build_licensee_data (39-68)
  • get_licensee (18-35)
  • init_licensee (72-91)
  • seed_licensee (95-99)
seed/context.py (3)
  • Context (12-33)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/buyer.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/module.py (2)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/catalog/product.py (5)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
mpt_api_client/resources/catalog/products.py (1)
  • Product (53-54)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/seller.py (5)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_seller.py (1)
  • seller (11-12)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_buyer.py (2)
seed/accounts/buyer.py (4)
  • build_buyer_data (39-61)
  • get_buyer (18-35)
  • init_buyer (65-85)
  • seed_buyer (89-93)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
tests/seed/accounts/test_user_group.py (2)
seed/accounts/user_group.py (4)
  • build_user_group_data (37-52)
  • get_user_group (16-33)
  • init_user_group (56-76)
  • seed_user_group (80-85)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (95-99)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
seed/accounts/user_group.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
🪛 Ruff (0.14.7)
seed/accounts/licensee.py

45-45: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
tests/e2e/accounts/sellers/conftest.py (1)

18-25: City change in seller_factory address looks good

Switching the fixture city to "Los Angeles" is fine and keeps the structure of the seeded seller data unchanged. Just ensure any assertions or external seed data that previously depended on the old city value remain consistent (e.g., string comparisons or filters by city).

seed/accounts/buyer.py (2)

64-85: LGTM!

The function properly validates the creation result with isinstance check and raises an explicit exception on failure. The re-fetch after creation ensures the full buyer object is retrieved.


88-93: LGTM!

The seed function follows the established pattern, delegating to init_buyer() with dependency injection handling the defaults.

seed/accounts/api_tokens.py (1)

70-75: LGTM!

The seed function follows the established pattern.

seed/accounts/user_group.py (2)

55-76: LGTM with note on inconsistency.

The function properly validates the creation result. However, note that this returns None on failure (line 76) while init_buyer raises ValueError. Consider aligning the error handling approach across modules for consistency.


79-85: LGTM!

The seed function follows the established pattern with return type aligned to init_user_group.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 6edaaa8 to 6ba1eef Compare December 3, 2025 14:10
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
tests/seed/accounts/test_buyer.py (2)

36-60: Test will fail: missing CLIENT_ACCOUNT_ID environment variable and seller ID.

Per seed/accounts/buyer.py lines 41-44, build_buyer_data reads CLIENT_ACCOUNT_ID from os.getenv() and accounts.seller.id from context. The test sets context["accounts.account.id"] (unused key) but never sets the environment variable or seller ID.

Apply this diff to fix:

-def test_build_buyer_data(context: Context) -> None:
-    context["accounts.account.id"] = "ACC-1086-6867"
+def test_build_buyer_data(context: Context, monkeypatch) -> None:
+    monkeypatch.setenv("CLIENT_ACCOUNT_ID", "ACC-1086-6867")
+    context["accounts.seller.id"] = "SEL-123"
     expected_data = {
         "name": "E2E Seeded Buyer",
         "account": {
             "id": "ACC-1086-6867",
         },
+        "sellers": [{"id": "SEL-123"}],
         "contact": {
             "firstName": "first",
             "lastName": "last",
             "email": "created.buyer@example.com",
         },
         "address": {
             "addressLine1": "123 Main St",
             "city": "Los Angeles",
             "state": "CA",
             "postCode": "12345",
             "country": "US",
         },
-        "sellers": [{"id": ""}],
     }

76-92: Test setup will fail due to eager evaluation of build_buyer_data(context).

Line 87 evaluates build_buyer_data(context) during patch setup, which will raise ValueError because CLIENT_ACCOUNT_ID is not set and accounts.seller.id is missing from context.

Apply this diff to fix:

 async def test_init_buyer_create_new(
-    context: Context, operations_client, buyers_service, buyer
+    context: Context, operations_client, buyers_service, buyer, monkeypatch
 ) -> None:
     buyers_service.create.return_value = buyer
     operations_client.accounts.buyers = buyers_service
 
-    context["accounts.account.id"] = "ACC-1086-6867"
+    monkeypatch.setenv("CLIENT_ACCOUNT_ID", "ACC-1086-6867")
+    context["accounts.seller.id"] = "SEL-123"
     buyers_service.get.return_value = buyer
     with (
         patch("seed.accounts.buyer.get_buyer", return_value=None),
         patch("pathlib.Path.open", return_value=AsyncMock()),
-        patch("seed.accounts.buyer.build_buyer_data", return_value=build_buyer_data(context)),
+        patch("seed.accounts.buyer.build_buyer_data", return_value={
+            "name": "E2E Seeded Buyer",
+            "account": {"id": "ACC-1086-6867"},
+            "sellers": [{"id": "SEL-123"}],
+        }),
     ):
         result = await init_buyer(context=context, mpt_operations=operations_client)
seed/accounts/buyer.py (1)

30-34: Add type validation after API fetch.

When fetching buyer from API on line 31, the result is stored in context without verifying it's a valid Buyer instance. If the API returns an error or unexpected type, this could corrupt the context.

Apply this diff:

     if not isinstance(buyer, Buyer):
         buyer = await mpt_operations.accounts.buyers.get(buyer_id)
+        if not isinstance(buyer, Buyer):
+            return None
         context.set_resource("accounts.buyer", buyer)
         context["accounts.buyer.id"] = buyer.id
         return buyer
seed/accounts/api_tokens.py (1)

35-48: Validate required configuration before building payload.

The function reads CLIENT_ACCOUNT_ID from the environment (line 40) and module_id from context (line 41) without validating that they contain valid values. If CLIENT_ACCOUNT_ID is unset, account_id will be None. If the module hasn't been seeded yet, module_id will be an empty string. This leads to confusing API errors rather than clear, actionable failures.

Apply this diff:

 @inject
 def build_api_token_data(
     context: Context = DEFAULT_CONTEXT,
 ) -> dict[str, object]:
     """Get API token data dictionary for creation."""
     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     module_id = context.get_string("accounts.module.id")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")  # noqa: TRY003
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first")  # noqa: TRY003
     return {
         "account": {"id": account_id},
         "name": "E2E Seeded API Token",

Based on learnings, this validation pattern is used in user_group.py at lines 41-43 and should be consistently applied here.

seed/accounts/user_group.py (1)

28-32: Add type validation after API fetch.

After fetching the user group from the API (line 29), the code assumes the result is a valid UserGroup and immediately accesses its .id attribute (line 31). If the API returns an unexpected type, this will cause an AttributeError at runtime.

Based on learnings from past reviews and the pattern used in init_user_group (line 69), add validation:

Apply this diff:

     if not isinstance(user_group, UserGroup):
         user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
+        if not isinstance(user_group, UserGroup):
+            logger.warning("API returned non-UserGroup type for id: %s", user_group_id)
+            return None
         context.set_resource("accounts.user_group", user_group)
         context["accounts.user_group.id"] = user_group.id
         return user_group
🧹 Nitpick comments (9)
tests/seed/accounts/test_licensee.py (1)

110-130: Consider consolidating duplicate tests.

test_init_licensee and test_init_licensee_create_new appear to test the same creation path (both patch get_licensee to return None and verify create is called). Consider either removing one or differentiating their test scenarios.

seed/accounts/seller.py (1)

1-1: Remove the mypy directive on line 1 if it's no longer needed.

The # mypy: disable-error-code=unreachable directive appears unnecessary. The code has no unreachable paths: get_seller has reachable early returns and a final return, while init_seller properly handles all branches with appropriate returns for both success and failure cases. Consider removing the directive and running mypy to confirm it no longer reports unreachable code errors.

tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused noqa directive.

The static analyzer correctly flagged that WPS210 is not recognized. This directive can be safely removed.

Apply this diff:

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:
seed/accounts/accounts.py (1)

13-13: Remove unused noqa directive.

The WPS217 code is not recognized by the current linter configuration.

Apply this diff:

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
seed/accounts/module.py (1)

69-69: Consider addressing the TRY003 linting hint.

The static analyzer flagged the long exception message. While not critical, you can either extract the message to a module-level constant or add a suppression comment if the explicit message is preferred.

Apply this diff to suppress:

-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError("Could not seed module: no valid Module found.")  # noqa: TRY003

Or extract to a constant:

+MODULE_SEED_ERROR = "Could not seed module: no valid Module found."
+
 @inject
 async def seed_module() -> Module:
     """Seed module."""
     logger.debug("Seeding module ...")
     existing_module = await get_module()
     if existing_module is None:
         refreshed = await refresh_module()
         logger.debug("Seeding module completed.")
         if refreshed is None:
-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError(MODULE_SEED_ERROR)
seed/accounts/user_group.py (2)

1-1: Consider removing the mypy suppression.

The return types now correctly reflect that functions can return None (lines 19, 59, 80), so the unreachable suppression may no longer be necessary.

Apply this diff:

-# mypy: disable-error-code=unreachable
 import logging

Then verify with mypy that no type errors are reported.


43-43: Consider addressing the TRY003 linting hint.

Similar to module.py, the long exception message is flagged. This is a style issue, not a defect.

Apply this diff to suppress:

-        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")  # noqa: TRY003
seed/catalog/product.py (2)

50-65: init_product get‑or‑create flow looks good; consider forwarding dependencies into get_product

The new get or create behavior is sound: if get_product() finds an existing product, you now log and return it; otherwise you create, persist in context, and return the created Product. That matches the seeding use case and avoids unnecessary re‑creation.

One small refactor: since init_product already accepts context and mpt_vendor, calling get_product() without arguments means any explicit arguments passed to init_product won’t be used by get_product. To keep things consistent and more testable, you could forward them:

-    product = await get_product()
+    product = await get_product(context=context, mpt_vendor=mpt_vendor)

This preserves current behavior under DI but avoids surprises if init_product is ever called with non‑default parameters.


53-63: Minor nits: deduplicate failure message and consider more idiomatic Path usage

Two tiny cleanups you might consider (purely optional):

  • You log "Product creation failed" and then raise ValueError("Product creation failed"). To avoid duplicating the literal, you could introduce a small module‑level constant (or a dedicated exception) and reuse it in both places.
  • with pathlib.Path.open(icon, "rb") as icon_file: works, but with icon.open("rb") as icon_file: is a bit more idiomatic and reads more clearly.

Not critical, but they slightly improve readability and future maintenance.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6edaaa8 and 6ba1eef.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • tests/e2e/accounts/licensees/conftest.py
  • tests/seed/test_seed_api.py
  • tests/seed/accounts/test_seller.py
  • pyproject.toml
  • tests/seed/accounts/test_api_token.py
  • seed/container.py
  • tests/seed/catalog/test_product.py
  • tests/seed/conftest.py
  • seed/seed_api.py
🧰 Additional context used
🧬 Code graph analysis (12)
tests/seed/accounts/test_module.py (5)
mpt_api_client/resources/accounts/modules.py (2)
  • AsyncModulesService (29-32)
  • Module (11-12)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
tests/seed/conftest.py (2)
  • context (15-16)
  • operations_client (25-26)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/user_group.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/api_tokens.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/licensee.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/seller.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_licensee.py (3)
mpt_api_client/resources/accounts/licensees.py (2)
  • AsyncLicenseesService (48-58)
  • Licensee (21-22)
seed/accounts/licensee.py (4)
  • build_licensee_data (39-68)
  • get_licensee (18-35)
  • init_licensee (72-91)
  • seed_licensee (95-99)
seed/context.py (3)
  • Context (12-33)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_buyer.py (3)
seed/accounts/buyer.py (4)
  • build_buyer_data (39-61)
  • get_buyer (18-35)
  • init_buyer (65-85)
  • seed_buyer (89-93)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/module.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_user_group.py (4)
mpt_api_client/resources/accounts/user_groups.py (2)
  • AsyncUserGroupsService (32-38)
  • UserGroup (11-12)
seed/accounts/user_group.py (4)
  • build_user_group_data (37-52)
  • get_user_group (16-33)
  • init_user_group (56-76)
  • seed_user_group (80-85)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (95-99)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
seed/catalog/product.py (5)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
tests/e2e/conftest.py (1)
  • logger (63-64)
mpt_api_client/resources/catalog/products.py (1)
  • Product (53-54)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
🪛 Ruff (0.14.7)
seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

45-45: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (14)
tests/e2e/accounts/sellers/conftest.py (1)

20-20: LGTM! Test data updated to align with seeded e2e data.

The change from "Anytown" to "Los Angeles" aligns with the PR objective of adding seeded accounts e2e data. The fixture now uses realistic, consistent test values throughout (including address line, state, postal code, and country). No other files reference "Anytown", so this change is isolated and safe.

tests/e2e/accounts/buyers/conftest.py (1)

30-36: LGTM! Address data now consistent with seeded accounts.

The city update to "Los Angeles" aligns with the buyer seed data in seed/accounts/buyer.py, ensuring e2e test fixtures match the seeded account structure.

tests/seed/accounts/test_buyer.py (1)

63-73: LGTM!

The test correctly patches get_buyer to return an existing buyer and verifies that create is not called when a buyer already exists.

seed/accounts/seller.py (1)

55-74: LGTM! Type validation and DI parameter forwarding now correct.

The init_seller function correctly:

  • Forwards context and mpt_operations to get_seller (line 61)
  • Validates the API response type with isinstance(created, Seller) (line 66)
  • Returns None on failure, matching the declared return type

Past review issues have been addressed.

seed/accounts/licensee.py (1)

71-91: LGTM! Proper DI forwarding and type validation.

The init_licensee function correctly:

  • Forwards context and mpt_client to get_licensee and build_licensee_data
  • Validates the API response type with isinstance(created, Licensee)
  • Uses proper error handling with clear logging

Past review issues have been addressed.

tests/seed/accounts/test_licensee.py (1)

85-107: LGTM! Test setup now correctly configures environment before patch evaluation.

The test properly sets the environment variable and context values (lines 91-95) before entering the with block, ensuring build_licensee_data(context) at line 101 has access to required values when evaluated during patch setup.

seed/accounts/buyer.py (1)

64-85: LGTM! Proper DI forwarding and type validation.

The init_buyer function correctly:

  • Forwards context and mpt_operations to get_buyer and build_buyer_data (lines 70, 72)
  • Validates the API response type with isinstance(created, Buyer) (line 76)
  • Re-fetches the buyer after creation to get the complete object (line 77)
  • Uses proper error handling with clear logging

Past review issues regarding DI parameter forwarding have been addressed.

tests/seed/accounts/test_module.py (1)

1-102: LGTM!

All previously identified issues have been resolved:

  • AsyncMock is correctly used for patching async functions (lines 61, 81, 94)
  • Exception chaining is properly implemented in DummyAsyncIterator.__anext__ (line 21)
  • Test coverage for get_module, refresh_module, and seed_module is comprehensive
tests/seed/accounts/test_accounts.py (1)

1-30: LGTM!

The test comprehensively validates that seed_accounts() orchestrates all six seeding functions correctly.

seed/accounts/accounts.py (1)

13-23: LGTM!

The orchestration sequentially seeds all account-related entities with appropriate logging.

tests/seed/accounts/test_user_group.py (1)

1-89: LGTM!

All previously identified issues have been resolved:

  • CLIENT_ACCOUNT_ID environment variable is properly set using monkeypatch in tests (lines 48, 65)
  • Duplicate test has been removed
  • AsyncMock is correctly used for patching async functions (line 85)
  • Test coverage comprehensively validates user group seeding workflows
seed/accounts/module.py (1)

14-72: LGTM!

The module seeding implementation is well-structured:

  • Proper dependency injection with @inject decorator
  • Context-aware resource management with appropriate caching
  • Fallback to API fetch when context resource is unavailable
  • Type validation using isinstance checks
  • Clear separation of concerns: get, refresh, and seed operations
seed/accounts/user_group.py (1)

36-52: LGTM!

The user group seeding implementation shows good practices:

  • Environment variable validation with clear error messages (lines 41-43)
  • Type validation for created resources (line 69)
  • Proper dependency injection parameter forwarding (lines 61, 67)
  • Appropriate logging for creation vs. reuse scenarios

Also applies to: 55-85

seed/accounts/api_tokens.py (1)

51-67: LGTM!

The parameter forwarding has been correctly implemented:

  • context and mpt_ops are properly passed to get_api_token (line 57)
  • context is correctly forwarded to build_api_token_data (line 60)
  • Logging distinguishes between creation and discovery scenarios

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 6ba1eef to 43c79cb Compare December 3, 2025 15:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (8)
tests/seed/catalog/test_product.py (1)

49-52: Remove redundant pathlib.Path.open patch in test setup.

Lines 51–52 patch pathlib.Path.open twice with the same lambda; the second patch just overrides the first and adds noise without changing behavior. A single patch is sufficient and clearer.

You can simplify to:

     with (
         patch("seed.catalog.product.get_product", new_callable=AsyncMock) as mock_get_product,
         patch("seed.catalog.product.icon", new=MagicMock()),
-        patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
-        patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
+        patch("pathlib.Path.open", new=lambda *_, **__: fake_file_factory()),
     ):

This keeps the behavior while avoiding redundant patching and matches the earlier recommendation.

seed/accounts/module.py (1)

61-71: Tighten seed_module completion logging and fix TRY003 on the error message

  • logger.debug("Seeding module completed.") executes before checking refreshed is None, so failures still log as “completed”.
  • Ruff flags the long ValueError message (TRY003).

You can fix both by introducing a constant and moving the debug log after the success check:

@@
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
+
+MODULE_SEED_ERROR = "Could not seed module: no valid Module found."
@@
 async def seed_module() -> Module:
@@
     logger.debug("Seeding module ...")
     existing_module = await get_module()
     if existing_module is None:
         refreshed = await refresh_module()
-        logger.debug("Seeding module completed.")
-        if refreshed is None:
-            raise ValueError("Could not seed module: no valid Module found.")
-        return refreshed
-    logger.debug("Seeding module completed.")
-    return existing_module
+        if refreshed is None:
+            raise ValueError(MODULE_SEED_ERROR)
+        logger.debug("Seeding module completed.")
+        return refreshed
+    logger.debug("Seeding module completed.")
+    return existing_module

This keeps logs accurate and satisfies Ruff without suppressing TRY003.

seed/accounts/user_group.py (2)

1-1: Remove broad mypy unreachable suppression

Now that init_user_group and seed_user_group honestly return UserGroup | None, the top‑level # mypy: disable-error-code=unreachable is likely no longer needed and may hide future issues. Consider dropping it and addressing any remaining mypy warnings more locally if they appear.

-# mypy: disable-error-code=unreachable

24-32: Add type validation after fetching user group from the API

mpt_operations.accounts.user_groups.get(user_group_id) is assumed to return a UserGroup, but if it doesn’t, context.set_resource(...) and user_group.id can fail.

Align this with the defensive pattern used elsewhere:

     except ValueError:
         user_group = None
     if not isinstance(user_group, UserGroup):
         user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
-        context.set_resource("accounts.user_group", user_group)
-        context["accounts.user_group.id"] = user_group.id
-        return user_group
+        if not isinstance(user_group, UserGroup):
+            logger.warning(
+                "accounts.user_groups.get returned unexpected type for id %s",
+                user_group_id,
+            )
+            return None
+        context.set_resource("accounts.user_group", user_group)
+        context["accounts.user_group.id"] = user_group.id
+        return user_group
seed/accounts/seller.py (1)

1-1: Consider removing global mypy unreachable suppression

With init_seller now returning Seller | None and handling the failure path explicitly, the top‑level # mypy: disable-error-code=unreachable is likely no longer required and can hide future real issues. Removing it should be safe once mypy runs cleanly:

-# mypy: disable-error-code=unreachable
seed/accounts/api_tokens.py (2)

23-31: Guard against non-ApiToken values after API get

If mpt_ops.accounts.api_tokens.get(api_token_id) returns something other than an ApiToken, the subsequent context.set_resource(...) and api_token.id access will fail.

A defensive pattern similar to other seeders would help:

     except ValueError:
         api_token = None
     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
-        context.set_resource("accounts.api_token", api_token)
-        context["accounts.api_token.id"] = api_token.id
-        return api_token
+        if not isinstance(api_token, ApiToken):
+            logger.warning(
+                "accounts.api_tokens.get returned unexpected type for id %s",
+                api_token_id,
+            )
+            return None
+        context.set_resource("accounts.api_token", api_token)
+        context["accounts.api_token.id"] = api_token.id
+        return api_token

36-48: Validate CLIENT_ACCOUNT_ID and module id before building API token payload

build_api_token_data currently builds a payload even when:

  • CLIENT_ACCOUNT_ID is unset (account_id becomes None), or
  • accounts.module.id is missing from the context (empty string).

That can lead to confusing downstream API errors. Failing fast here is easier to diagnose:

 def build_api_token_data(
     context: Context = DEFAULT_CONTEXT,
 ) -> dict[str, object]:
     """Get API token data dictionary for creation."""
     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     module_id = context.get_string("accounts.module.id")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is not set.")
+    if not module_id:
+        raise ValueError(
+            "accounts.module.id missing from context; seed module before API token."
+        )
     return {
         "account": {"id": account_id},
@@
         "modules": [{"id": module_id}],
     }
seed/accounts/buyer.py (1)

17-35: Add type validation after API fetch.

When fetching the buyer from API (line 31), the result is stored in context without verifying it's a valid Buyer instance. If the API returns an error or unexpected type, this could corrupt the context and cause downstream failures.

     if not isinstance(buyer, Buyer):
         buyer = await mpt_operations.accounts.buyers.get(buyer_id)
+        if not isinstance(buyer, Buyer):
+            logger.warning("Failed to fetch buyer: %s", buyer_id)
+            return None
         context.set_resource("accounts.buyer", buyer)
         context["accounts.buyer.id"] = buyer.id
         return buyer
🧹 Nitpick comments (8)
seed/catalog/product.py (1)

45-52: Thread context and mpt_vendor through to get_product for consistency.

At Line 50, init_product ignores its context and mpt_vendor parameters when calling get_product, relying on the defaults instead. This is surprising and can break callers that intentionally pass a custom context or vendor client, since lookup happens against the default context/client while creation uses the provided ones.

Recommend wiring these through:

 async def init_product(
     context: Context = DEFAULT_CONTEXT,
     mpt_vendor: AsyncMPTClient = DEFAULT_MPT_VENDOR,
 ) -> Product:
     """Get or create product."""
-    product = await get_product()
+    product = await get_product(context=context, mpt_vendor=mpt_vendor)

This keeps existing defaulted behavior intact while making the function’s parameters actually respected.

seed/accounts/module.py (1)

40-41: refresh_module docstring doesn’t match behavior

The docstring says “always fetch”, but the implementation returns the cached module without any API call when get_module finds one. Either update the docstring to describe the actual “fetch-if-missing, otherwise reuse cached” behavior or adjust the implementation to always re-fetch and refresh the context.

seed/accounts/user_group.py (1)

41-43: Optional: Extract CLIENT_ACCOUNT_ID error message to a constant (TRY003)

Ruff flags the inline ValueError message here (TRY003). If you want a clean lint run without suppression, you can move the string to a constant:

-import logging
+import logging
@@
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
+
+MISSING_CLIENT_ACCOUNT_ID_ERROR = (
+    "CLIENT_ACCOUNT_ID environment variable is required"
+)
@@
-    account_id = os.getenv("CLIENT_ACCOUNT_ID")
-    if not account_id:
-        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
+    account_id = os.getenv("CLIENT_ACCOUNT_ID")
+    if not account_id:
+        raise ValueError(MISSING_CLIENT_ACCOUNT_ID_ERROR)

Alternatively, if your team prefers, add # noqa: TRY003 to this line instead.

seed/accounts/api_tokens.py (1)

51-67: Optionally validate type after create for symmetry with other seeders

Unlike init_seller/init_user_group, init_api_token assumes create(...) always returns a valid ApiToken. If the API ever responds with an error model, api_token.id will raise.

For consistency and robustness, you might mirror the pattern used elsewhere:

     if api_token is None:
         logger.debug("Creating API token ...")
         api_token_data = build_api_token_data(context=context)
-        api_token = await mpt_ops.accounts.api_tokens.create(api_token_data)
-        context.set_resource("accounts.api_token", api_token)
-        context["accounts.api_token.id"] = api_token.id
-        logger.info("API token created: %s", api_token.id)
+        created = await mpt_ops.accounts.api_tokens.create(api_token_data)
+        if isinstance(created, ApiToken):
+            context.set_resource("accounts.api_token", created)
+            context["accounts.api_token.id"] = created.id
+            logger.info("API token created: %s", created.id)
+            return created
+        logger.warning("API token creation failed")
+        raise ValueError("API token creation failed")
@@
-    return api_token
+    return api_token
tests/seed/accounts/test_licensee.py (1)

85-130: Consider consolidating or differentiating these tests.

test_init_licensee and test_init_licensee_create_new are functionally identical—both mock get_licensee to return None and verify the create path. Either remove the duplicate or differentiate them to test distinct scenarios (e.g., one for existing licensee, one for new creation).

To test the "existing licensee" path:

 async def test_init_licensee(
-    context: Context, client_client, licensees_service, licensee, monkeypatch
+    context: Context, client_client, licensees_service, licensee
 ) -> None:
-    licensees_service.create.return_value = licensee
+    licensees_service.get.return_value = licensee
     client_client.accounts.licensees = licensees_service
 
-    monkeypatch.setenv("CLIENT_ACCOUNT_ID", "ACC-1086-6867")
-    context["accounts.user_group.id"] = "UG-123"
-    context.set_resource("accounts.user_group", Model({"id": "UG-123"}))
-    context["accounts.seller.id"] = "SEL-123"
-    context["accounts.buyer.id"] = "BUY-123"
+    context["accounts.licensee.id"] = "LIC-123"
     with (
-        patch("seed.accounts.licensee.get_licensee", return_value=None),
-        patch("pathlib.Path.open", return_value=AsyncMock()),
-        patch(
-            "seed.accounts.licensee.build_licensee_data",
-            return_value=build_licensee_data(context),
-        ),
+        patch("seed.accounts.licensee.get_licensee", return_value=licensee),
     ):
         result = await init_licensee(context=context, mpt_client=client_client)
 
     assert result == licensee
-    licensees_service.create.assert_called_once()
+    licensees_service.create.assert_not_called()
seed/accounts/licensee.py (1)

71-91: Use idiomatic Path method call.

Line 81 uses pathlib.Path.open(icon, "rb") which works but is unconventional. The idiomatic form is icon.open("rb") since icon is already a Path object.

-        with pathlib.Path.open(icon, "rb") as icon_file:
+        with icon.open("rb") as icon_file:
seed/accounts/buyer.py (2)

64-85: Inconsistent pattern: extra API call after creation.

Line 77 refetches the buyer after creation (await mpt_operations.accounts.buyers.get(created.id)), while licensee.py uses the created object directly. This adds an extra API call and is inconsistent.

Either align with the licensee pattern:

         if isinstance(created, Buyer):
-            buyer = await mpt_operations.accounts.buyers.get(created.id)
-            context.set_resource("accounts.buyer", buyer)
-            context["accounts.buyer.id"] = buyer.id
-            logger.info("Buyer created: %s", buyer.id)
-            return buyer
+            context.set_resource("accounts.buyer", created)
+            context["accounts.buyer.id"] = created.id
+            logger.info("Buyer created: %s", created.id)
+            return created

Or if the refetch is intentional (e.g., to get complete data not returned by create), add a comment explaining why and apply the same pattern to licensee.py for consistency.


74-74: Use idiomatic Path method call.

Same issue as in licensee.py: use icon.open("rb") instead of pathlib.Path.open(icon, "rb").

-        with pathlib.Path.open(icon, "rb") as icon_file:
+        with icon.open("rb") as icon_file:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ba1eef and 43c79cb.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • tests/e2e/accounts/buyers/conftest.py
  • tests/e2e/accounts/licensees/conftest.py
  • pyproject.toml
  • tests/seed/conftest.py
  • seed/seed_api.py
  • tests/seed/accounts/test_api_token.py
  • tests/seed/accounts/test_user_group.py
  • tests/seed/accounts/test_seller.py
  • seed/container.py
  • tests/e2e/accounts/sellers/conftest.py
  • tests/seed/accounts/test_buyer.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (9)
seed/accounts/seller.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
tests/seed/catalog/test_product.py (4)
tests/seed/conftest.py (3)
  • context (15-16)
  • vendor_client (20-21)
  • fake_file_factory (35-36)
tests/seed/catalog/test_item_group.py (1)
  • vendor_client (29-30)
tests/seed/catalog/test_product_parameters.py (1)
  • vendor_client (30-31)
seed/catalog/product.py (1)
  • init_product (45-65)
seed/accounts/buyer.py (3)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/api_tokens.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_api_token.py (1)
  • api_token (15-16)
seed/accounts/licensee.py (5)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_licensee.py (1)
  • licensee (12-30)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (95-99)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
seed/catalog/product.py (4)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
mpt_api_client/resources/catalog/products.py (1)
  • Product (53-54)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/user_group.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
🪛 Ruff (0.14.7)
tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

45-45: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (11)
seed/accounts/user_group.py (1)

79-85: seed_user_group contract looks good

seed_user_group simply defers to init_user_group and returns the resulting UserGroup | None with clear start/end logging. This matches the pattern used by other account seeders and keeps the API simple.

tests/seed/test_seed_api.py (1)

23-39: Test update correctly tracks seed_accounts orchestration

Patching seed.seed_api.seed_accounts with AsyncMock and asserting it’s called once keeps test_seed_api_success aligned with the new seeding flow while still verifying context load/save behavior.

tests/seed/accounts/test_module.py (1)

10-101: Module seeding tests cover key async paths correctly

Using AsyncMock for get_module/refresh_module and the DummyAsyncIterator for filtered modules gives good coverage of both cache‑hit and refresh paths, plus the seed_module fallback behavior.

seed/accounts/module.py (1)

24-32: Remove unnecessary isinstance check after API call; address unhandled exceptions instead

The API's get() method returns Module or raises an exception (TypeError, HTTPError)—it cannot return None or a non-Module type due to the type system and from_response() implementation. The isinstance(module, Module) check on lines 27–28 is redundant and will never fail if no exception occurred.

The actual risk is unhandled exceptions from malformed API responses or HTTP errors, which propagate uncaught. Consider wrapping the API call in a try-except block to handle potential failures gracefully:

if not isinstance(module, Module):
    try:
        module = await mpt_operations.accounts.modules.get(module_id)
    except (TypeError, HTTPError):
        logger.warning("Failed to fetch module %s from API", module_id)
        return None
    context.set_resource("accounts.module", module)
    context["accounts.module.id"] = module.id
    return module

Likely an incorrect or invalid review comment.

tests/seed/accounts/test_licensee.py (2)

11-35: LGTM! Fixtures are well-defined.

The fixtures provide appropriate mock data for testing the licensee functionality.


133-137: LGTM!

The orchestration test correctly verifies that init_licensee is invoked.

seed/accounts/licensee.py (3)

17-35: LGTM!

The get_licensee function correctly handles cache lookup, API fetch fallback, and context updates.


38-68: LGTM!

The build_licensee_data function properly validates the environment variable and constructs the payload using context values.


94-99: LGTM!

The seed_licensee function correctly orchestrates the seeding with appropriate logging.

seed/accounts/buyer.py (2)

38-61: LGTM!

The build_buyer_data function properly validates the environment variable and constructs the payload.


88-93: LGTM!

The seed_buyer function correctly orchestrates the seeding with appropriate logging.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 43c79cb to e1e9586 Compare December 3, 2025 15:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (9)
seed/accounts/seller.py (1)

28-32: Add type validation after API get call.

When fetching from the API on line 29, the result is used directly without verifying it's a valid Seller instance. If mpt_operations.accounts.sellers.get(seller_id) returns an unexpected type, accessing seller.id on line 31 will fail with an unclear AttributeError.

Apply this diff:

     if not isinstance(seller, Seller):
         seller = await mpt_operations.accounts.sellers.get(seller_id)
+        if not isinstance(seller, Seller):
+            logger.warning("Failed to fetch seller: %s", seller_id)
+            return None
         context.set_resource("accounts.seller", seller)
         context["accounts.seller.id"] = seller.id
         return seller
seed/accounts/user_group.py (2)

28-32: Add type validation after API get call.

Same issue as in seller.py: when fetching from API on line 29, the result is used without verifying it's a valid UserGroup instance.

     if not isinstance(user_group, UserGroup):
         user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
+        if not isinstance(user_group, UserGroup):
+            logger.warning("Failed to fetch user group: %s", user_group_id)
+            return None
         context.set_resource("accounts.user_group", user_group)
         context["accounts.user_group.id"] = user_group.id
         return user_group

44-52: Validate module_id before building payload.

context.get_string("accounts.module.id") returns an empty string if the key is missing. This would result in a payload containing "modules": [{"id": ""}], likely causing an API error.

     module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError(
+            "accounts.module.id missing from context; seed module before user group."
+        )
     return {
         "name": "E2E Seeded User Group",
seed/accounts/module.py (1)

60-71: Resolve Ruff TRY003 on ValueError message in seed_module

Ruff still flags the inline error message at Line 69 (TRY003). To satisfy the linter and keep the explicit text, either extract the message into a constant or add a targeted suppression.

For example:

 logger = logging.getLogger(__name__)
 
+MODULE_SEED_ERROR_MSG = "Could not seed module: no valid Module found."
+
 ...
-        if refreshed is None:
-            raise ValueError("Could not seed module: no valid Module found.")
+        if refreshed is None:
+            raise ValueError(MODULE_SEED_ERROR_MSG)

(or, if you prefer to keep it inline, append # noqa: TRY003).

seed/accounts/buyer.py (1)

17-35: (Repeat) Validate Buyer type after API fetch before caching

get_buyer refetches via mpt_operations.accounts.buyers.get(buyer_id) and then unconditionally caches/uses the result. If the API ever returns None or a non‑Buyer payload, you can end up with a bad object in the context or hit an AttributeError on buyer.id.

Consider adding an explicit type check here, similar to:

     if not isinstance(buyer, Buyer):
         buyer = await mpt_operations.accounts.buyers.get(buyer_id)
-        context.set_resource("accounts.buyer", buyer)
-        context["accounts.buyer.id"] = buyer.id
-        return buyer
+        if not isinstance(buyer, Buyer):
+            logger.warning("Buyer %s fetched from API is not a Buyer instance.", buyer_id)
+            return None
+        context.set_resource("accounts.buyer", buyer)
+        context["accounts.buyer.id"] = buyer.id
+        return buyer

This mirrors the defensive pattern you’re using elsewhere and keeps the context consistent.

seed/accounts/accounts.py (1)

13-23: Remove unused # noqa: WPS217 on seed_accounts

Ruff reports the # noqa: WPS217 on seed_accounts as an unused suppression (RUF100). The function body is straightforward, so the directive is no longer needed:

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:

This will keep the file lint‑clean without changing behavior.

tests/seed/accounts/test_accounts.py (1)

6-30: Drop unused # noqa: WPS210 from test_seed_accounts

The # noqa: WPS210 on the test function is reported as an unused suppression (RUF100), and the test body is small enough not to need it. You can safely remove it:

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:

No behavioral change, and it keeps lint output cleaner.

seed/accounts/api_tokens.py (2)

27-31: Add type validation after API fetch.

After fetching from the API (line 28), the code assumes the result is a valid ApiToken and directly accesses its .id attribute (line 30). If the API returns None or an unexpected type, this will raise an AttributeError.

Apply this diff to add validation:

     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
+        if not isinstance(api_token, ApiToken):
+            logger.warning("API returned non-ApiToken type for id: %s", api_token_id)
+            return None
         context.set_resource("accounts.api_token", api_token)
         context["accounts.api_token.id"] = api_token.id
         return api_token

35-48: Validate CLIENT_ACCOUNT_ID and module id before building payload.

Right now, build_api_token_data will construct a payload even if:

  • CLIENT_ACCOUNT_ID is not set (line 40, account_id becomes None), or
  • accounts.module.id is missing from context (line 41, empty string default).

This can lead to confusing API errors or tokens associated with invalid ids. Fail fast with a clear error instead.

Apply this diff to add validation:

 @inject
 def build_api_token_data(
     context: Context = DEFAULT_CONTEXT,
 ) -> dict[str, object]:
     """Get API token data dictionary for creation."""
     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     module_id = context.get_string("accounts.module.id")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is not set.")
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first.")
     return {
         "account": {"id": account_id},
         "name": "E2E Seeded API Token",
         "description": "This is a seeded API token for end-to-end testing.",
         "icon": "",
         "modules": [{"id": module_id}],
     }
🧹 Nitpick comments (10)
tests/seed/accounts/test_module.py (1)

34-36: Remove unused helper function.

The async_iter function is defined but never used in this test file. It appears to be leftover code.

-def async_iter(iter_items):
-    yield from iter_items
-
-
tests/seed/accounts/test_seller.py (2)

60-63: Use AsyncMock for patching async get_seller.

get_seller is an async function, but it's patched with return_value=None which creates a non-async mock. While this may work in this specific test because init_seller only checks if the result is None, it's inconsistent with the pattern used elsewhere (e.g., test_seed_seller on line 85) and could mask issues in future test modifications.

-    with patch("seed.accounts.seller.get_seller", return_value=None):
+    with patch("seed.accounts.seller.get_seller", new_callable=AsyncMock, return_value=None):
         result = await init_seller(context=context, mpt_operations=operations_client)

71-78: Use AsyncMock for patching async get_seller.

Same issue as above—get_seller is async and should be patched with AsyncMock for consistency.

     with (
-        patch("seed.accounts.seller.get_seller", return_value=None),
+        patch("seed.accounts.seller.get_seller", new_callable=AsyncMock, return_value=None),
         patch(
             "seed.accounts.seller.build_seller_data",
             return_value=build_seller_data("test-external-id"),
         ),
     ):
seed/accounts/user_group.py (1)

1-1: Consider removing the mypy suppression.

Similar to seller.py, this directive may no longer be needed since return types are now correctly annotated as UserGroup | None. Verify if mypy passes without this suppression.

seed/accounts/seller.py (1)

1-1: Remove the mypy suppression directive.

The # mypy: disable-error-code=unreachable at line 1 is no longer necessary. All code paths in this file are reachable: the return statements at lines 32–33 and 70–74 are properly conditioned, and there are no unreachable code branches. With the corrected Seller | None return types already in place, mypy should pass without this suppression.

seed/accounts/module.py (2)

15-32: Validate API response type before caching the module

After mpt_operations.accounts.modules.get(module_id) you assume the result is a Module and immediately cache it and access module.id. If the API ever returns None or an unexpected type, this will either break with AttributeError or pollute the context with a bad object.

Consider guarding the cache/update with an explicit type check and logging when the type is wrong, e.g.:

     if not isinstance(module, Module):
-        module = await mpt_operations.accounts.modules.get(module_id)
-        context.set_resource("accounts.module", module)
-        context["accounts.module.id"] = module.id
-        return module
+        module = await mpt_operations.accounts.modules.get(module_id)
+        if not isinstance(module, Module):
+            logger.warning("Module %s fetched from API is not a Module instance.", module_id)
+            return None
+        context.set_resource("accounts.module", module)
+        context["accounts.module.id"] = module.id
+        return module

This keeps the context consistent and lets callers handle a missing/invalid module via the existing None path.


36-57: Align refresh_module docstring with its behavior

The docstring says “always fetch”, but refresh_module delegates to get_module and returns the cached module when it exists, only hitting the API when there is no module. Either update the docstring to reflect the current behavior or change the implementation to always refetch when a module ID is present.

seed/accounts/licensee.py (2)

17-35: Type‑check API licensee before caching into context

In get_licensee, if the cached value is missing/invalid you refetch via mpt_client.accounts.licensees.get(licensee_id) and then unconditionally cache and use the result. If the API returns None or some non‑Licensee model, this will break or corrupt the cached resource.

Consider mirroring a defensive pattern here:

     if not isinstance(licensee, Licensee):
-        licensee = await mpt_client.accounts.licensees.get(licensee_id)
-        context.set_resource("accounts.licensee", licensee)
-        context["accounts.licensee.id"] = licensee.id
-        return licensee
+        licensee = await mpt_client.accounts.licensees.get(licensee_id)
+        if not isinstance(licensee, Licensee):
+            logger.warning(
+                "Licensee %s fetched from API is not a Licensee instance.",
+                licensee_id,
+            )
+            return None
+        context.set_resource("accounts.licensee", licensee)
+        context["accounts.licensee.id"] = licensee.id
+        return licensee

That keeps the context consistent and preserves the None contract for callers.


39-89: Tidy up long ValueError messages flagged by Ruff (TRY003)

Ruff is flagging the inline ValueError messages in build_licensee_data (Line 45) and init_licensee (Line 89). To keep the explicit messages while satisfying the linter, either:

  • Extract them into module‑level constants and use those in the raises/logs, or
  • Append a focused suppression like # noqa: TRY003 on those lines if your team is comfortable with that.

Centralizing the strings is a small cleanup that also avoids duplicating error text.

seed/accounts/buyer.py (1)

38-61: Resolve Ruff TRY003 for Buyer‑related ValueError messages

Ruff is flagging the inline ValueError messages in build_buyer_data (Line 43) and init_buyer (Line 83). To keep the descriptive messages without tripping TRY003, you can:

  • Extract them into module‑level constants and reuse them, or
  • Add explicit # noqa: TRY003 on those raise lines if you intentionally keep the literals inline.

Using constants has the nice side effect of centralizing user‑facing error text for reuse.

Also applies to: 64-85

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43c79cb and e1e9586.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • seed/container.py
  • tests/e2e/accounts/buyers/conftest.py
  • tests/seed/catalog/test_product.py
  • tests/seed/accounts/test_user_group.py
  • tests/e2e/accounts/sellers/conftest.py
  • tests/seed/accounts/test_buyer.py
  • tests/seed/accounts/test_api_token.py
  • tests/seed/conftest.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-01T10:48:52.586Z
Learnt from: jentyk
Repo: softwareone-platform/mpt-api-python-client PR: 136
File: tests/e2e/notifications/subscribers/conftest.py:14-25
Timestamp: 2025-12-01T10:48:52.586Z
Learning: The repository softwareone-platform/mpt-api-python-client uses wemake-python-styleguide linter, so noqa directives for WPS rules should not be flagged as unused.

Applied to files:

  • pyproject.toml
🧬 Code graph analysis (11)
tests/seed/accounts/test_module.py (4)
mpt_api_client/resources/accounts/modules.py (2)
  • AsyncModulesService (29-32)
  • Module (11-12)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (89-93)
seed/accounts/licensee.py (1)
  • seed_licensee (95-99)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/accounts/module.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/buyer.py (2)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/seed_api.py (4)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
tests/seed/conftest.py (1)
  • context (15-16)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
tests/seed/accounts/test_seller.py (2)
seed/accounts/seller.py (4)
  • build_seller_data (37-52)
  • get_seller (16-33)
  • init_seller (56-74)
  • seed_seller (78-82)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/api_tokens.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/accounts/seller.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/user_group.py (5)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_user_group.py (1)
  • user_group (16-17)
seed/catalog/product.py (5)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
mpt_api_client/http/mixins.py (6)
  • create (18-26)
  • create (86-112)
  • create (118-144)
  • create (190-216)
  • create (262-270)
  • create (331-358)
mpt_api_client/resources/catalog/products.py (1)
  • Product (53-54)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
🪛 Ruff (0.14.7)
seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/accounts/licensee.py

45-45: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (11)
pyproject.toml (1)

117-135: LGTM!

The per-file-ignores additions for seed/accounts/*.py and the accounts resources are appropriate for the new seeding modules. The WPS453 suppression aligns with the patterns used in similar seeding code. Based on learnings, this repository uses wemake-python-styleguide, so these WPS rule suppressions are valid.

tests/e2e/accounts/licensees/conftest.py (1)

29-35: LGTM!

The address data update aligns the test fixture with the seeding module conventions, maintaining consistency across the test suite.

seed/catalog/product.py (1)

51-65: LGTM!

The restructured control flow is clear and defensive:

  • Guards creation behind the None check
  • Validates the API response type with isinstance(created, Product) before use
  • Provides explicit logging for both success and failure paths
  • Raises a clear exception on creation failure

This pattern is consistent with other seed modules in the PR.

tests/seed/accounts/test_module.py (1)

92-101: LGTM!

The test properly uses AsyncMock for patching the async get_module and refresh_module functions, addressing the previous review feedback. The pattern correctly sets up the mock return value before invoking the function under test.

tests/seed/accounts/test_seller.py (1)

83-88: LGTM!

The test_seed_seller correctly uses AsyncMock for patching the async init_seller function, following proper async testing patterns.

seed/accounts/user_group.py (1)

55-76: LGTM!

The init_user_group function now properly:

  • Validates the created resource with isinstance(created, UserGroup) before use
  • Returns None on failure with appropriate logging
  • Correctly passes injected parameters to get_user_group
tests/seed/test_seed_api.py (1)

23-39: Updated orchestration test for seed_accounts looks correct

Patching seed.seed_api.seed_accounts alongside seed_catalog and asserting both mocks are called once cleanly exercises the new concurrent seeding path without hitting real I/O. This aligns with the updated seed_api implementation.

seed/seed_api.py (1)

20-33: Concurrent catalog/accounts seeding and context persistence look good

Using asyncio.create_task plus asyncio.gather to run seed_catalog() and seed_accounts() in parallel, while wrapping the gather in a try/except and always calling save_context in finally, is a solid pattern for this orchestration. Behavior matches the updated tests and keeps context persistence robust even on failures.

seed/accounts/api_tokens.py (3)

1-11: LGTM!

Imports and logger setup are clean and appropriate for the module's functionality.


51-67: Parameter propagation issue resolved.

The function now correctly passes context and mpt_ops to both get_api_token (line 57) and build_api_token_data (line 60), ensuring that non-default dependencies are properly respected. This fix enables correct behavior for tests and alternative contexts.


70-75: LGTM!

The wrapper function correctly delegates to init_api_token, which will use its own injected defaults. The logging provides appropriate visibility into the seeding process.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from e1e9586 to 14e8cfa Compare December 3, 2025 15:51
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (10)
seed/accounts/user_group.py (3)

28-32: Add type validation after API fetch.

When fetching from API on line 29, the result is used without verifying it's a valid UserGroup instance. If the API returns an error or unexpected type, setting context resources will fail.

Apply this diff:

     if not isinstance(user_group, UserGroup):
         user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
+        if not isinstance(user_group, UserGroup):
+            return None
         context.set_resource("accounts.user_group", user_group)
         context["accounts.user_group.id"] = user_group.id
         return user_group

44-52: Add validation for module_id before building user group payload.

context.get_string("accounts.module.id") returns an empty string if the key is missing. This would produce "modules": [{"id": ""}] in the payload, causing an API error.

Apply this diff:

     if not account_id:
         raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
     module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError(
+            "accounts.module.id missing from context; seed module before user group."
+        )
     return {

1-1: Remove the mypy suppression now that return types are aligned.

The # mypy: disable-error-code=unreachable suppression at line 1 was needed when the return type was UserGroup but the function could return None. Now that init_user_group and seed_user_group correctly return UserGroup | None, this suppression can be safely removed.

-# mypy: disable-error-code=unreachable
 import logging
seed/accounts/accounts.py (1)

13-13: Remove unused # noqa: WPS217 directive.

Ruff reports this as an unused noqa (RUF100). Since the function body is straightforward, drop the directive:

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused # noqa: WPS210 directive.

Ruff flags this as an unused suppression (RUF100). The test function is straightforward and doesn't trigger WPS210.

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:
seed/accounts/module.py (1)

69-69: Address Ruff TRY003 on the ValueError message.

Ruff flags the long exception message. Consider suppressing with # noqa: TRY003 if the explicit message is preferred for clarity.

-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError("Could not seed module: no valid Module found.")  # noqa: TRY003
seed/accounts/seller.py (1)

28-32: Add type validation after API get call.

After fetching via mpt_operations.accounts.sellers.get(seller_id), the code accesses seller.id without confirming the result is a Seller. If the API returns an unexpected type, this will raise an AttributeError.

     if not isinstance(seller, Seller):
         seller = await mpt_operations.accounts.sellers.get(seller_id)
+        if not isinstance(seller, Seller):
+            logger.warning("Unexpected type returned for seller id %s", seller_id)
+            return None
         context.set_resource("accounts.seller", seller)
         context["accounts.seller.id"] = seller.id
         return seller

This matches the defensive pattern used in init_seller at lines 66-72.

seed/accounts/api_tokens.py (2)

27-31: Add type validation after API fetch.

After fetching the API token from the API on line 28, the code assumes the result is a valid ApiToken instance and directly accesses api_token.id on line 30. If the API returns an unexpected type or error response, this will raise an AttributeError.

Apply this diff to add validation:

     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
+        if not isinstance(api_token, ApiToken):
+            logger.warning("API returned non-ApiToken type for id: %s", api_token_id)
+            return None
         context.set_resource("accounts.api_token", api_token)
         context["accounts.api_token.id"] = api_token.id
         return api_token

35-48: Validate required environment variable and context value.

The function reads CLIENT_ACCOUNT_ID from the environment (line 40) and accounts.module.id from context (line 41) without validation. If either is missing, the API creation call will fail with an unclear error.

Apply this diff to add validation:

 @inject
 def build_api_token_data(
     context: Context = DEFAULT_CONTEXT,
 ) -> dict[str, object]:
     """Get API token data dictionary for creation."""
     account_id = os.getenv("CLIENT_ACCOUNT_ID")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
     module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first")
     return {
         "account": {"id": account_id},
seed/accounts/buyer.py (1)

27-34: Add type validation after API fetch.

After fetching the buyer from the API on line 31, the code assumes the result is a valid Buyer instance and directly accesses buyer.id on line 33. If the API returns an unexpected type or error response, this will raise an AttributeError.

Apply this diff to add validation:

     if not isinstance(buyer, Buyer):
         buyer = await mpt_operations.accounts.buyers.get(buyer_id)
+        if not isinstance(buyer, Buyer):
+            logger.warning("API returned non-Buyer type for id: %s", buyer_id)
+            return None
         context.set_resource("accounts.buyer", buyer)
         context["accounts.buyer.id"] = buyer.id
         return buyer
🧹 Nitpick comments (3)
tests/seed/accounts/test_module.py (1)

34-35: Remove unused async_iter function.

This helper function is not used anywhere in the test file. DummyAsyncIterator is used instead for async iteration.

-def async_iter(iter_items):
-    yield from iter_items
-
-
 async def test_get_module(context: Context, operations_client, module, modules_service) -> None:
tests/seed/accounts/test_seller.py (1)

56-80: Consider consolidating similar tests.

test_init_seller and test_init_seller_create_new both test the "create new seller" path by patching get_seller to return None. The second test adds a patch for build_seller_data but doesn't provide additional coverage since create.assert_called_once() is the same assertion.

Consider removing one or differentiating them (e.g., test the "existing seller" path in one test).

seed/accounts/api_tokens.py (1)

51-67: Consider validating API token creation result.

While the parameters are now correctly passed (addressing previous feedback), the code assumes create() on line 61 returns a valid ApiToken and accesses .id on line 63 without validation. Although API creation typically fails loudly, adding validation would make error handling more explicit and consistent with the pattern used in init_buyer.

Consider applying this diff:

     if api_token is None:
         logger.debug("Creating API token ...")
         api_token_data = build_api_token_data(context=context)
         api_token = await mpt_ops.accounts.api_tokens.create(api_token_data)
+        if not isinstance(api_token, ApiToken):
+            logger.warning("API token creation failed")
+            raise ValueError("API token creation failed")
         context.set_resource("accounts.api_token", api_token)
         context["accounts.api_token.id"] = api_token.id
         logger.info("API token created: %s", api_token.id)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1e9586 and 14e8cfa.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (1 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/e2e/accounts/licensees/conftest.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • tests/e2e/accounts/buyers/conftest.py
  • tests/seed/accounts/test_buyer.py
  • seed/container.py
  • tests/e2e/accounts/sellers/conftest.py
  • pyproject.toml
  • tests/seed/conftest.py
  • tests/seed/catalog/test_product.py
  • tests/seed/test_seed_api.py
🧰 Additional context used
🧬 Code graph analysis (9)
tests/seed/accounts/test_licensee.py (4)
mpt_api_client/models/model.py (2)
  • Model (65-125)
  • id (119-121)
mpt_api_client/resources/accounts/licensees.py (2)
  • AsyncLicenseesService (48-58)
  • Licensee (21-22)
seed/accounts/licensee.py (4)
  • build_licensee_data (39-68)
  • get_licensee (18-35)
  • init_licensee (72-91)
  • seed_licensee (95-99)
seed/context.py (3)
  • Context (12-33)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_user_group.py (3)
seed/accounts/user_group.py (4)
  • build_user_group_data (37-52)
  • get_user_group (16-33)
  • init_user_group (56-76)
  • seed_user_group (80-85)
tests/seed/conftest.py (2)
  • context (15-16)
  • operations_client (25-26)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/module.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/seed_api.py (4)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
tests/seed/conftest.py (1)
  • context (15-16)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
seed/accounts/user_group.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/accounts/api_tokens.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_api_token.py (1)
  • api_token (15-16)
seed/accounts/seller.py (3)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/buyer.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
🪛 Ruff (0.14.7)
seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

seed/accounts/licensee.py

45-45: Avoid specifying long messages outside the exception class

(TRY003)


89-89: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (32)
seed/catalog/product.py (1)

51-65: LGTM! Clean control flow with proper type validation.

The refactored logic correctly guards creation when no product exists, validates the API response type, and returns the existing product otherwise. The isinstance(created, Product) check follows the same pattern used in other seeders.

seed/seed_api.py (1)

20-32: LGTM! Proper parallel seeding with robust error handling.

The parallel execution of seed_catalog() and seed_accounts() is appropriate since these are independent operations. The exception handling and finally block ensure context is persisted regardless of success or failure.

seed/accounts/accounts.py (1)

14-23: LGTM! Correct seeding order with proper dependency handling.

The sequential execution ensures seed_module() completes before seed_user_group(), which depends on module_id in context. The logging provides good visibility into the seeding lifecycle.

tests/seed/accounts/test_api_token.py (5)

1-21: LGTM! Well-structured test fixtures.

The fixtures properly create mock objects with appropriate specs, enabling type-safe testing of async API interactions.


24-37: LGTM! Good coverage of get_api_token paths.

The tests cover both the successful retrieval case and the edge case where no ID exists in context.


40-54: LGTM! Proper test of the creation path.

The test correctly patches dependencies to isolate the creation logic and verifies the API call is made.


57-70: LGTM! Correct environment variable handling.

Using monkeypatch.setenv properly isolates the test from the actual environment while verifying the payload structure.


73-78: LGTM! Concise orchestration test.

The test verifies that seed_api_token properly delegates to init_api_token.

tests/seed/accounts/test_module.py (5)

10-21: LGTM! Proper async iterator with exception chaining.

The DummyAsyncIterator correctly chains StopAsyncIteration from err, addressing the B904 lint rule.


38-51: LGTM! Good test coverage for get_module.

Both the successful retrieval and the missing ID edge case are properly tested.


54-70: LGTM! Proper async mocking for refresh_module.

The test correctly uses AsyncMock for the async get_module function and DummyAsyncIterator for filter iteration.


73-89: LGTM! Tests the new module retrieval path.

The test verifies the module refresh behavior when fetching a new module through iteration.


92-102: LGTM! Proper async orchestration test.

The test correctly uses AsyncMock for both patched async functions and verifies the delegation to refresh_module.

tests/seed/accounts/test_accounts.py (1)

7-30: LGTM!

The test correctly patches all six seed functions called by seed_accounts() and verifies each is invoked exactly once. The use of AsyncMock with new_callable ensures proper async behavior.

seed/accounts/seller.py (3)

36-52: LGTM!

The build_seller_data function correctly generates a unique externalId when none is provided and constructs the required payload structure.


55-74: LGTM!

The init_seller function correctly forwards injected dependencies to get_seller, validates the API response type before caching, and handles both success and failure paths appropriately.


77-82: LGTM!

The seed_seller orchestration function correctly relies on dependency injection for defaults and provides appropriate logging.

tests/seed/accounts/test_seller.py (4)

10-17: LGTM!

Fixtures are well-defined with proper specs for type safety and clear test data.


20-33: LGTM!

Both get_seller tests are well-structured, covering the happy path with context caching and the early return when no ID is present.


36-53: LGTM!

The test validates the complete payload structure and correctly verifies the externalId passthrough.


83-88: LGTM!

The test correctly verifies that seed_seller invokes init_seller exactly once.

tests/seed/accounts/test_user_group.py (4)

15-22: LGTM!

Fixtures are properly defined with AsyncMock and appropriate specs.


25-40: LGTM!

Both get_user_group tests properly verify retrieval with caching and the early return path.


43-61: LGTM!

The test correctly uses monkeypatch.setenv to set the required environment variable before the patch setup evaluates build_user_group_data(context). This addresses the previously flagged issue.


64-89: LGTM!

Both tests are well-structured. test_build_user_group_data correctly uses monkeypatch for the environment variable, and test_seed_user_group properly verifies the orchestration.

seed/accounts/module.py (1)

35-57: LGTM!

The refresh_module function correctly forwards dependencies, uses proper type validation for the filtered results, and handles edge cases with appropriate logging.

seed/accounts/licensee.py (3)

38-68: LGTM!

The build_licensee_data function correctly validates the required CLIENT_ACCOUNT_ID environment variable and properly retrieves dependent resources from context.


71-91: LGTM!

The init_licensee function correctly forwards dependencies, uses proper file handling with context manager, and validates the API response type before caching.


94-99: LGTM!

The seed_licensee orchestration function correctly relies on dependency injection and provides appropriate logging.

tests/seed/accounts/test_licensee.py (1)

11-35: Licensee seeding tests comprehensively cover env/context wiring and async flows

Fixtures, env setup, and AsyncMock-based patches now align with the production seed.accounts.licensee logic (including CLIENT_ACCOUNT_ID and pathlib.Path.open usage). Both the lookup and create paths for init_licensee plus the top-level seed_licensee orchestration are exercised, and I don’t see remaining correctness issues here.

Also applies to: 38-52, 54-83, 85-138, 141-146

seed/accounts/buyer.py (2)

38-61: LGTM!

The validation of CLIENT_ACCOUNT_ID properly addresses the previous review comment, and the function correctly constructs the buyer payload from environment and context.


64-85: LGTM!

The function correctly passes injected parameters to downstream calls (addressing previous feedback) and implements a robust create-or-get pattern with proper validation and error handling.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 91b8405 to 7b9f708 Compare December 4, 2025 12:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
seed/catalog/product.py (1)

87-96: Propagate context into get_product in publish_product.

publish_product accepts a context parameter but currently calls get_product() with no arguments, which means a caller-provided context is ignored and the default context is used instead. This can desync behavior in tests or multi-context scenarios.

Consider:

 async def publish_product(
     context: Context = DEFAULT_CONTEXT,
     mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
 ) -> Product | None:
     """Publish product if in reviewing status."""
-    product = await get_product()
+    product = await get_product(context=context)
♻️ Duplicate comments (14)
seed/accounts/module.py (2)

61-69: Address Ruff TRY003 on ValueError message in seed_module.

Ruff flags the long literal message here. To keep the explicit error while satisfying the linter, either extract it to a constant or add a targeted suppression:

+MODULE_SEED_ERROR = "Could not seed module: no valid Module found."
...
-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError(MODULE_SEED_ERROR)

or:

-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError("Could not seed module: no valid Module found.")  # noqa: TRY003

20-32: Add type validation after mpt_operations.accounts.modules.get before using module.id.

After an API failure or unexpected response, module might not be a Module, but it’s written into context and module.id is accessed unconditionally. This can corrupt context and raise at runtime.

Consider:

    if not isinstance(module, Module):
        module = await mpt_operations.accounts.modules.get(module_id)
-        context.set_resource("accounts.module", module)
-        context["accounts.module.id"] = module.id
-        return module
+        if not isinstance(module, Module):
+            logger.warning("Unexpected type returned for module id %s", module_id)
+            return None
+        context.set_resource("accounts.module", module)
+        context["accounts.module.id"] = module.id
+        return module
seed/accounts/user_group.py (3)

1-1: Remove now-unnecessary mypy unreachable suppression.

init_user_group and seed_user_group now return UserGroup | None, so there is no unreachable branch. The top-level # mypy: disable-error-code=unreachable can be dropped to let type-checking work normally.

[save]


21-33: Add type validation after mpt_operations.accounts.user_groups.get before using user_group.id.

Similar to buyer/module, this path assumes the API returns a UserGroup and immediately writes it into context.

Suggested pattern:

    if not isinstance(user_group, UserGroup):
        user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
-        context.set_resource("accounts.user_group", user_group)
-        context["accounts.user_group.id"] = user_group.id
-        return user_group
+        if not isinstance(user_group, UserGroup):
+            logger.warning("Unexpected type returned for user group id %s", user_group_id)
+            return None
+        context.set_resource("accounts.user_group", user_group)
+        context["accounts.user_group.id"] = user_group.id
+        return user_group

41-52: Validate module_id before building payload (and consider TRY003).

module_id = context.get_string("accounts.module.id") can produce an empty string, which would send "modules": [{"id": ""}] to the API and likely fail with a less clear error. Also, Ruff flags the long ValueError message for CLIENT_ACCOUNT_ID.

You can:

    account_id = os.getenv("CLIENT_ACCOUNT_ID")
    if not account_id:
-        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")  # noqa: TRY003
    module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError(
+            "accounts.module.id missing from context; seed module before user group."
+        )  # noqa: TRY003

or extract the messages into constants if you prefer not to use noqa.

seed/accounts/accounts.py (1)

13-21: Remove unused # noqa: WPS217 on seed_accounts.

Ruff reports this as an unused/unknown suppression. The function body is short, so the directive isn’t needed—dropping it keeps lint output clean.

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
seed/accounts/buyer.py (1)

23-35: Validate type after mpt_operations.accounts.buyers.get before using/storing.

If the API ever returns an unexpected type, buyer.id and the cached resource will be wrong, leading to confusing runtime errors.

You can mirror the pattern used elsewhere:

    if not isinstance(buyer, Buyer):
        buyer = await mpt_operations.accounts.buyers.get(buyer_id)
-        context.set_resource("accounts.buyer", buyer)
-        context["accounts.buyer.id"] = buyer.id
-        return buyer
+        if not isinstance(buyer, Buyer):
+            logger.warning("Unexpected type returned for buyer id %s", buyer_id)
+            return None
+        context.set_resource("accounts.buyer", buyer)
+        context["accounts.buyer.id"] = buyer.id
+        return buyer
tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused noqa directive.

The # noqa: WPS210 suppression is flagged as unused by Ruff. Since the test is simple enough and doesn't trigger WPS210, the suppression should be removed:

-async def test_seed_accounts() -> None:  # noqa: WPS210
+async def test_seed_accounts() -> None:

Based on static analysis hints and past review comments.

tests/seed/catalog/test_product.py (1)

51-52: Remove duplicate pathlib.Path.open patch.

Lines 51 and 52 both patch pathlib.Path.open, causing the second patch to override the first. This creates dead code and confusion.

     with (
         patch("seed.catalog.product.get_product", new_callable=AsyncMock) as mock_get_product,
         patch("seed.catalog.product.icon", new=MagicMock()),
-        patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
-        patch("pathlib.Path.open", new=lambda *_, **_unused: fake_file_factory()),
+        patch("pathlib.Path.open", new=lambda *_, **__: fake_file_factory()),
     ):

Based on past review comments.

seed/accounts/seller.py (1)

28-33: Add type validation after API get call.

Line 29 calls await mpt_operations.accounts.sellers.get(seller_id) and immediately accesses seller.id on line 31 without validating the response type. If the API returns an unexpected type or error response, this will raise an AttributeError.

     if not isinstance(seller, Seller):
         seller = await mpt_operations.accounts.sellers.get(seller_id)
+        if not isinstance(seller, Seller):
+            logger.warning(
+                "accounts.sellers.get returned unexpected type for id %s",
+                seller_id,
+            )
+            return None
         context.set_resource("accounts.seller", seller)
         context["accounts.seller.id"] = seller.id
         return seller

This adds the same defensive check used in init_seller (lines 66-72).

Based on past review comments.

seed/accounts/licensee.py (2)

39-54: Tighten build_licensee_data validation and clean up linter noise.

A few issues are clustered here:

  • Context.get_resource("accounts.user_group") either returns a Model or raises ValueError; the if group is None: branch is effectively unreachable and never surfaces your clearer error.
  • Ruff reports RUF100 for the # noqa: WPS238 directive because that code is no longer associated with a known rule; leaving it in produces unnecessary lint noise.
  • Ruff also flags TRY003 on the long ValueError messages, which you may want to address by centralizing messages or using dedicated exception types/constants.

You can improve clarity and satisfy the linters with something like:

-@inject
-def build_licensee_data(  # noqa: WPS238
+@inject
+def build_licensee_data(
@@
-    group = context.get_resource("accounts.user_group")
-    if group is None:
-        raise ValueError("User group is required in context")
+    try:
+        group = context.get_resource("accounts.user_group")
+    except ValueError as exc:
+        raise ValueError(
+            "accounts.user_group missing from context; seed user group before licensee."
+        ) from exc  # noqa: TRY003

Optionally, you can also extract the four validation checks into a small helper (as suggested earlier) to completely remove the need for WPS238 suppression.


30-34: Add defensive type check after API get in get_licensee.

After falling back to await mpt_client.accounts.licensees.get(licensee_id), you assume the result is a Licensee and immediately access .id. If the API or client ever returns an unexpected type, this will either crash or pollute the context with an invalid resource.

Recommend validating the type before using it, mirroring the earlier suggestion:

     if not isinstance(licensee, Licensee):
         licensee = await mpt_client.accounts.licensees.get(licensee_id)
-        context.set_resource("accounts.licensee", licensee)
-        context["accounts.licensee.id"] = licensee.id
-        return licensee
+        if not isinstance(licensee, Licensee):
+            logger.warning("Unexpected type returned for licensee id: %s", licensee_id)
+            return None
+        context.set_resource("accounts.licensee", licensee)
+        context["accounts.licensee.id"] = licensee.id
+        return licensee

This keeps get_licensee robust against bad responses while preserving the existing “get or create” behavior in init_licensee.

seed/accounts/api_tokens.py (2)

27-32: Add type validation after mpt_ops.accounts.api_tokens.get in get_api_token.

After the fallback fetch, the code assumes the result is an ApiToken and immediately accesses .id. If the API or client returns an unexpected type, this will fail or contaminate the context.

Recommend guarding the result:

     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
-        context.set_resource("accounts.api_token", api_token)
-        context["accounts.api_token.id"] = api_token.id
-        return api_token
+        if not isinstance(api_token, ApiToken):
+            logger.warning("API returned non-ApiToken type for id: %s", api_token_id)
+            return None
+        context.set_resource("accounts.api_token", api_token)
+        context["accounts.api_token.id"] = api_token.id
+        return api_token

This keeps get_api_token safe without changing its public contract.


35-48: Validate CLIENT_ACCOUNT_ID and accounts.module.id before building the API token payload.

Right now, account_id may be None and module_id may be an empty string, but the function still returns a payload. That can lead to confusing API errors or tokens tied to invalid IDs.

Align this with the stricter validation you use for licensee seeding:

     """Get API token data dictionary for creation."""
     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     module_id = context.get_string("accounts.module.id")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is not set.")
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first.")
     return {
         "account": {"id": account_id},

Failing fast here makes seeding failures easier to diagnose and avoids sending malformed requests to the API.

🧹 Nitpick comments (8)
seed/accounts/module.py (1)

40-57: Align refresh_module docstring with behavior (or always re-fetch).

The docstring says “always fetch”, but the implementation returns the cached module when present and only queries by name when nothing is found. Either update the docstring to reflect this “refresh if missing” behavior, or change the code to always re-fetch from the API when a module is already in context.

seed/accounts/buyer.py (1)

41-47: Handle Ruff TRY003 for explicit ValueError messages.

The three raised ValueErrors here use relatively long literal messages and are flagged by Ruff. If these messages are important to keep as-is, either:

  • Extract them into named constants and reference those, or
  • Add # noqa: TRY003 to the specific lines.

Example constant approach:

+CLIENT_ACCOUNT_ID_REQUIRED = "CLIENT_ACCOUNT_ID environment variable is required"
+SELLER_ID_MISSING = "accounts.seller.id missing from context; seed seller before buyer."
+BUYER_CREATION_FAILED = "Buyer creation failed"
...
-    if not buyer_account_id:
-        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
+    if not buyer_account_id:
+        raise ValueError(CLIENT_ACCOUNT_ID_REQUIRED)
...
-    if not seller_id:
-        raise ValueError("accounts.seller.id missing from context; seed seller before buyer.")
+    if not seller_id:
+        raise ValueError(SELLER_ID_MISSING)
...
-    logger.warning("Buyer creation failed")
-    raise ValueError("Buyer creation failed")
+    logger.warning(BUYER_CREATION_FAILED)
+    raise ValueError(BUYER_CREATION_FAILED)

Also applies to: 84-84

seed/catalog/product.py (1)

57-63: Optionally handle TRY003 on "Product creation failed" ValueError.

Ruff flags this string; if you want to keep the explicit message, either move it into a constant or add a focused noqa:

-PRODUCT_CREATION_FAILED_MSG = "Product creation failed"
...
-        logger.warning("Product creation failed")
-        raise ValueError("Product creation failed")
+        logger.warning(PRODUCT_CREATION_FAILED_MSG)
+        raise ValueError(PRODUCT_CREATION_FAILED_MSG)

or:

-        logger.warning("Product creation failed")
-        raise ValueError("Product creation failed")
+        logger.warning("Product creation failed")
+        raise ValueError("Product creation failed")  # noqa: TRY003
tests/seed/accounts/test_seller.py (1)

66-81: Consider removing or differentiating duplicate test.

test_init_seller_create_new is very similar to test_init_seller (lines 56-64). Both patch get_seller to return None and assert create() is called once. The only difference is the additional build_seller_data patch, which doesn't add meaningful coverage since init_seller calls build_seller_data() without arguments.

If the intent is to verify build_seller_data is called with specific arguments, add an assertion like mock_build_seller_data.assert_called_once_with(...). Otherwise, consider removing this duplicate test.

tests/seed/accounts/test_module.py (1)

54-90: Consider consolidating duplicate test_refresh_module tests.

test_refresh_module (lines 54-71) and test_refresh_module_get_new (lines 73-90) appear to be functionally identical—both set up the same mocks, context, and assertions. If they're testing the same behavior, consider consolidating them into a single test. If they're meant to test different scenarios, clarify the distinction with comments or different setup/assertions.

seed/accounts/seller.py (1)

1-1: Clarify the mypy: disable-error-code=unreachable directive.

The global mypy: disable-error-code=unreachable suppression suggests a type inconsistency that's being masked rather than fixed. While the current signature init_seller(...) -> Seller | None and the return None on line 72 are technically consistent, this pattern differs from similar functions in the codebase (e.g., init_product raises ValueError on failure).

Consider either:

  1. Removing the directive if it's no longer needed after recent fixes
  2. Aligning error handling with the init_product pattern (raise on failure instead of returning None)
  3. Adding a comment explaining why this directive is necessary

Based on past review comments and comparison with seed/catalog/product.py.

seed/accounts/licensee.py (1)

83-95: Improve robustness and lint-friendliness in init_licensee creation path.

The creation flow looks correct, but there are two minor points:

  • pathlib.Path.open(icon, "rb") works, but icon.open("rb") is clearer and more idiomatic.
  • Ruff flags TRY003 on raise ValueError("Licensee creation failed"); if TRY003 is enforced in CI, consider either using a dedicated exception type or moving the message to a constant to quiet the rule.

Example tweaks:

-        logger.debug("Creating licensee ...")
-        with pathlib.Path.open(icon, "rb") as icon_file:
+        logger.debug("Creating licensee ...")
+        with icon.open("rb") as icon_file:
@@
-        logger.warning("Licensee creation failed")
-        raise ValueError("Licensee creation failed")
+        logger.warning("Licensee creation failed")
+        raise ValueError("Licensee creation failed")  # noqa: TRY003

Or replace the ValueError with a small custom LicenseeCreationError to keep messages in the exception class instead of the call site.

seed/accounts/api_tokens.py (1)

51-67: Optionally guard the create response type in init_api_token.

You assume mpt_ops.accounts.api_tokens.create() always returns an ApiToken and immediately access .id. If that assumption ever breaks, logging will crash before you see the underlying issue.

Consider a lightweight check:

-        api_token = await mpt_ops.accounts.api_tokens.create(api_token_data)
-        context.set_resource("accounts.api_token", api_token)
-        context["accounts.api_token.id"] = api_token.id
-        logger.info("API token created: %s", api_token.id)
+        api_token = await mpt_ops.accounts.api_tokens.create(api_token_data)
+        if not isinstance(api_token, ApiToken):
+            logger.error("API returned non-ApiToken on create")
+            raise TypeError("Unexpected API token create response type")
+        context.set_resource("accounts.api_token", api_token)
+        context["accounts.api_token.id"] = api_token.id
+        logger.info("API token created: %s", api_token.id)

Not mandatory, but it will make any contract breakages much easier to debug.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91b8405 and 7b9f708.

⛔ Files ignored due to path filters (1)
  • seed/accounts/logo.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • pyproject.toml (2 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (2 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (1 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/e2e/accounts/licensees/conftest.py
  • tests/seed/accounts/test_licensee.py
  • tests/e2e/accounts/sellers/conftest.py
  • tests/e2e/accounts/buyers/conftest.py
  • tests/seed/accounts/test_buyer.py
  • pyproject.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/catalog/test_product.py
  • tests/seed/conftest.py
🧬 Code graph analysis (11)
tests/seed/catalog/test_product.py (2)
tests/seed/conftest.py (2)
  • context (15-16)
  • fake_file_factory (35-36)
seed/catalog/product.py (1)
  • init_product (45-65)
seed/accounts/seller.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/sellers.py (1)
  • Seller (13-14)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_seller.py (3)
mpt_api_client/resources/accounts/sellers.py (2)
  • AsyncSellersService (44-60)
  • Seller (13-14)
seed/accounts/seller.py (4)
  • build_seller_data (37-52)
  • get_seller (16-33)
  • init_seller (56-74)
  • seed_seller (78-82)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
tests/seed/accounts/test_module.py (4)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
tests/seed/conftest.py (2)
  • context (15-16)
  • operations_client (25-26)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (90-94)
seed/accounts/licensee.py (1)
  • seed_licensee (101-105)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
seed/catalog/product.py (4)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
mpt_api_client/resources/catalog/products.py (1)
  • Product (53-54)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/api_tokens.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/conftest.py (1)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
seed/seed_api.py (3)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
seed/accounts/user_group.py (8)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/e2e/conftest.py (1)
  • logger (63-64)
tests/e2e/accounts/conftest.py (2)
  • user_group_id (30-31)
  • module_id (35-36)
tests/seed/accounts/test_user_group.py (1)
  • user_group (16-17)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
🪛 Ruff (0.14.7)
seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


46-46: Avoid specifying long messages outside the exception class

(TRY003)


84-84: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

39-39: Unused noqa directive (unknown: WPS238)

Remove unused noqa directive

(RUF100)


45-45: Avoid specifying long messages outside the exception class

(TRY003)


48-48: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)


95-95: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

63-63: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (10)
tests/seed/test_seed_api.py (1)

23-39: Test correctly asserts new seed_accounts integration.

Patching seed.seed_api.seed_accounts alongside seed_catalog and asserting both are called once cleanly verifies the updated orchestration without over-specifying implementation details.

seed/container.py (1)

48-68: Accounts seeding modules correctly wired into the container.

Adding seed.accounts and its submodules to wire_container aligns DI wiring with the new seeding workflow and should make the @inject usage in accounts seeders work as intended.

seed/catalog/product.py (2)

45-65: init_product creation path looks correct and aligns with DI pattern.

Using get_product(context=context, mpt_vendor=mpt_vendor) before creating, validating created with isinstance(Product), and only then populating context provides a clear and safe flow for seeding the product.


69-81: Context-aware get_product usage in review_product is consistent.

Passing context and mpt_vendor through to get_product ensures tests and callers using custom contexts/clients behave as expected while still caching refreshed products.

tests/seed/conftest.py (1)

1-12: New file/fixture helpers are straightforward and useful for tests.

fake_file and fake_file_factory give a clean way to inject file-like objects into seed tests, and client_client follows the same pattern as the other AsyncMPTClient mocks.

Also applies to: 29-36

seed/seed_api.py (1)

20-26: LGTM! Parallel seeding implementation is clean.

The parallel execution of catalog and accounts seeding via asyncio.gather is well-structured, with proper typing and error handling preserved.

tests/seed/accounts/test_api_token.py (1)

1-80: LGTM! Comprehensive test coverage with correct async mocking.

The test suite properly covers:

  • Resource retrieval with context caching
  • Initialization and creation flows
  • Payload construction with environment variable handling
  • Seed orchestration

The use of AsyncMock for async functions and monkeypatch for environment variables is appropriate.

tests/seed/accounts/test_user_group.py (1)

1-93: LGTM! Tests properly handle async mocking and environment variables.

The test suite correctly:

  • Uses AsyncMock for async functions
  • Sets environment variables via monkeypatch before they're needed
  • Validates context caching and resource storage
  • Covers initialization, data building, and seeding flows
seed/accounts/licensee.py (1)

100-105: seed_licensee flow looks good and consistent with other seeders.

The function logs start/end and delegates all work to init_licensee(), which keeps orchestration simple and consistent with the other seeding modules.

seed/accounts/api_tokens.py (1)

70-75: seed_api_token orchestration looks correct.

The seed function just logs and delegates to init_api_token(), which keeps the orchestration simple and matches the pattern used in other seed modules.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 7b9f708 to def7b8f Compare December 4, 2025 14:55
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (15)
seed/accounts/module.py (2)

27-31: Add type validation after API get call.

After fetching via mpt_operations.accounts.modules.get(module_id), the code accesses module.id without confirming the result is a Module. This could raise an AttributeError if the API returns an unexpected type.

Apply this diff:

     if not isinstance(module, Module):
         module = await mpt_operations.accounts.modules.get(module_id)
+        if not isinstance(module, Module):
+            logger.warning("Unexpected type returned for module id %s", module_id)
+            return None
         context.set_resource("accounts.module", module)
         context["accounts.module.id"] = module.id
         return module

60-72: Ruff TRY003: Address the ValueError message pattern.

Ruff flagged the ValueError at Line 69 (TRY003). To keep the explicit message while satisfying the linter, add a suppression comment.

-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError("Could not seed module: no valid Module found.")  # noqa: TRY003
seed/accounts/user_group.py (3)

1-1: Remove mypy suppression and align return types.

The # mypy: disable-error-code=unreachable suppression masks a type contract violation. init_user_group can return None (line 76), and the signature now correctly reflects -> UserGroup | None. The suppression appears unnecessary.

-# mypy: disable-error-code=unreachable
 import logging

28-31: Add type validation after API fetch.

When fetching from API on line 29, the result is used without verifying it's a valid UserGroup instance.

     if not isinstance(user_group, UserGroup):
         user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
+        if not isinstance(user_group, UserGroup):
+            return None
         context.set_resource("accounts.user_group", user_group)
         context["accounts.user_group.id"] = user_group.id
         return user_group

44-51: Validate module_id before building user group payload.

context.get_string("accounts.module.id") returns an empty string if the key is missing. This would result in an invalid payload "modules": [{"id": ""}], causing an API error.

     module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError(  # noqa: TRY003
+            "accounts.module.id missing from context; seed module before user group."
+        )
     return {
seed/accounts/accounts.py (1)

13-13: Remove unused # noqa: WPS217 directive.

Ruff reports this as an unused noqa (RUF100). The function body is straightforward and doesn't trigger WPS217.

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
seed/accounts/buyer.py (1)

30-34: Add type validation after API fetch.

After fetching buyer via mpt_operations.accounts.buyers.get(buyer_id), the code accesses buyer.id without confirming the result is a Buyer. This follows the same pattern flagged in other seed modules.

     if not isinstance(buyer, Buyer):
         buyer = await mpt_operations.accounts.buyers.get(buyer_id)
+        if not isinstance(buyer, Buyer):
+            logger.warning("Unexpected type returned for buyer id %s", buyer_id)
+            return None
         context.set_resource("accounts.buyer", buyer)
         context["accounts.buyer.id"] = buyer.id
         return buyer
tests/seed/accounts/test_buyer.py (1)

36-61: Test will fail: missing CLIENT_ACCOUNT_ID environment variable.

build_buyer_data reads CLIENT_ACCOUNT_ID from os.getenv(), not from context. The test sets context["accounts.account.id"] but never sets the environment variable, so the function will raise ValueError("CLIENT_ACCOUNT_ID environment variable is required").

Apply this diff:

-def test_build_buyer_data(context: Context):
-    context["accounts.account.id"] = "ACC-1086-6867"
+def test_build_buyer_data(context: Context, monkeypatch):
+    monkeypatch.setenv("CLIENT_ACCOUNT_ID", "ACC-1086-6867")
     context["accounts.seller.id"] = "SEL-9999-9999"
tests/seed/accounts/test_accounts.py (1)

6-6: Remove unused noqa directive.

The # noqa: WPS210 suppression is flagged as unused by Ruff.

Apply this diff:

-async def test_seed_accounts(mocker):  # noqa: WPS210
+async def test_seed_accounts(mocker):
seed/accounts/api_tokens.py (2)

27-31: Add type validation after API get call.

After fetching from the API (line 28), the code directly accesses api_token.id without verifying the result is an ApiToken. If the API returns an unexpected type, this will fail.

Apply this diff:

     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
+        if not isinstance(api_token, ApiToken):
+            logger.warning("API returned non-ApiToken type for id: %s", api_token_id)
+            return None
         context.set_resource("accounts.api_token", api_token)
         context["accounts.api_token.id"] = api_token.id
         return api_token

40-48: Validate required IDs before building payload.

CLIENT_ACCOUNT_ID may be unset (returning None), and accounts.module.id may be missing from context (returning an empty string). Using these without validation will produce invalid payloads like {"account": {"id": None}} or {"modules": [{"id": ""}]}, leading to confusing API errors.

Apply this diff:

     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     module_id = context.get_string("accounts.module.id")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")  # noqa: TRY003
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first")  # noqa: TRY003
     return {
         "account": {"id": account_id},
seed/accounts/licensee.py (2)

52-54: Fix unreachable None check for user group.

context.get_resource("accounts.user_group") either returns a Model or raises ValueError—it never returns None. The if group is None check on lines 53-54 is unreachable, so your clearer error message will never be shown.

Apply this diff to provide a helpful error when the user group is missing:

-    group = context.get_resource("accounts.user_group")
-    if group is None:
-        raise ValueError("User group is required in context")
+    try:
+        group = context.get_resource("accounts.user_group")
+    except ValueError as exc:
+        raise ValueError(
+            "accounts.user_group missing from context; seed user group before licensee."
+        ) from exc  # noqa: TRY003

30-34: Add type validation after API get call.

After fetching from the API (line 31), the code directly accesses licensee.id without verifying the result is a Licensee. If the API returns an unexpected type, this will fail at runtime.

Apply this diff:

     if not isinstance(licensee, Licensee):
         licensee = await mpt_client.accounts.licensees.get(licensee_id)
+        if not isinstance(licensee, Licensee):
+            logger.warning("API returned non-Licensee type for id: %s", licensee_id)
+            return None
         context.set_resource("accounts.licensee", licensee)
         context["accounts.licensee.id"] = licensee.id
         return licensee
seed/catalog/product.py (1)

86-101: Propagate context into get_product in publish_product to avoid context desync

publish_product accepts a context parameter but still calls get_product() with no arguments (Line 92). This means it always uses DEFAULT_CONTEXT instead of any explicitly passed context, which can desynchronize behavior compared to init_product/review_product and makes tests using a custom Context potentially misleading.

Consider aligning this with the other helpers:

 async def publish_product(
     context: Context = DEFAULT_CONTEXT,
     mpt_operations: AsyncMPTClient = DEFAULT_MPT_OPERATIONS,
 ) -> Product | None:
     """Publish product if in reviewing status."""
-    product = await get_product()
+    product = await get_product(context=context)

This keeps retrieval aligned with the caller’s context while still relying on the default vendor client injection.

tests/seed/catalog/test_product.py (1)

1-1: Direct AsyncMock import vs. project mocking conventions

There’s a prior review preference in this project for relying on pytest’s mocker fixture rather than importing from unittest.mock directly. Please double‑check that keeping from unittest.mock import AsyncMock aligns with agreed conventions here; if not, consider switching to a mocker-based approach and dropping this import.

🧹 Nitpick comments (16)
mpt_api_client/resources/commerce/assets.py (1)

24-40: Public rename may break consumers; consider BC aliases and docstring alignment

The introduction of AssetServiceConfig and the renaming to AssetService / AsyncAssetService keep behavior intact, but they do change the public class names. If external users were importing AssetsServiceConfig, AssetsService, or AsyncAssetsService, this will be a breaking change unless you’re planning a major version bump.

If you want a softer transition, consider adding backwards‑compatibility aliases and marking them as deprecated, e.g.:

+ # Backwards‑compatibility aliases (deprecated; remove in a future major release)
+ AssetsServiceConfig = AssetServiceConfig
+ AssetsService = AssetService
+ AsyncAssetsService = AsyncAssetService

Also, the docstrings still say “Assets service config.” / “Assets service.” — you might optionally update them to match the new singular class names for clarity.

Also applies to: 66-75

tests/e2e/conftest.py (1)

110-113: Make logo_fd path independent of current working directory

Using pathlib.Path("tests/data/logo.png").resolve() assumes tests are always run with the project root as CWD; running from tests/ or other dirs will break this path. You already have project_root_path, so you can build an absolute, project-root–relative path and avoid depending on CWD.

-@pytest.fixture
-def logo_fd():
-    file_path = pathlib.Path("tests/data/logo.png").resolve()
-    return file_path.open("rb")
+@pytest.fixture
+def logo_fd(project_root_path):
+    file_path = project_root_path / "tests" / "data" / "logo.png"
+    return file_path.open("rb")
seed/accounts/buyer.py (2)

14-14: Relative path may fail depending on working directory.

pathlib.Path("seed/data/logo.png").resolve() is evaluated at module load time and assumes the working directory is the repository root. If the module is imported from a different location, this path resolution will fail.

Consider using __file__ to construct a path relative to this module:

-icon = pathlib.Path("seed/data/logo.png").resolve()
+icon = pathlib.Path(__file__).parent.parent / "data" / "logo.png"

76-76: Remove unused noqa directive.

Ruff reports that # noqa: PTH123 is unused since the rule is not enabled.

-        with open(str(icon), "rb") as icon_file:  # noqa: PTH123
+        with open(str(icon), "rb") as icon_file:
tests/seed/accounts/test_buyer.py (1)

74-74: Remove unused noqa directive.

Ruff reports # noqa: WPS211 as unused since WPS211 is not enabled.

-async def test_init_buyer_create_new(  # noqa: WPS211
+async def test_init_buyer_create_new(
tests/seed/accounts/test_user_group.py (1)

41-41: Remove unused noqa directive.

Ruff flags the # noqa: WPS211 as unused.

Apply this diff:

-async def test_init_user_group(  # noqa: WPS211
+async def test_init_user_group(
tests/seed/accounts/test_licensee.py (2)

85-85: Remove unused noqa directives.

Both function definitions have # noqa: WPS211 suppressions flagged as unused by Ruff.

Apply this diff:

-async def test_init_licensee(  # noqa: WPS211
+async def test_init_licensee(
     context: Context, client_client, licensees_service, licensee, monkeypatch, mocker, fs
 ):
-async def test_init_licensee_create_new(  # noqa: WPS211
+async def test_init_licensee_create_new(
     context: Context, client_client, licensees_service, licensee, monkeypatch, fs, mocker
 ):

Also applies to: 106-106


106-119: Remove unused mocker parameter.

The test doesn't patch any functions, so the mocker fixture parameter is unused.

Apply this diff:

 async def test_init_licensee_create_new(
-    context: Context, client_client, licensees_service, licensee, monkeypatch, fs, mocker
+    context: Context, client_client, licensees_service, licensee, monkeypatch, fs
 ):
seed/accounts/licensee.py (3)

39-39: Remove unused noqa directive.

The # noqa: WPS238 suppression is no longer needed and is flagged as unused by Ruff.

Apply this diff:

-def build_licensee_data(  # noqa: WPS238
+def build_licensee_data(

45-45: Consider suppressing TRY003 for descriptive error messages.

Ruff flags several ValueError raises with long messages (TRY003). Since these messages provide helpful context about missing environment variables and seeding order, you may want to suppress the rule rather than extract them to constants.

Add # noqa: TRY003 to the affected raises:

-        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")  # noqa: TRY003
-        raise ValueError("Seller ID is required in context")
+        raise ValueError("Seller ID is required in context")  # noqa: TRY003
-        raise ValueError("Buyer ID is required in context")
+        raise ValueError("Buyer ID is required in context")  # noqa: TRY003
-        raise ValueError("Licensee creation failed")
+        raise ValueError("Licensee creation failed")  # noqa: TRY003

Also applies to: 48-48, 51-51, 95-95


87-87: Remove unused noqa directive.

The # noqa: PTH123 suppression is flagged as unused (the rule is not enabled).

Apply this diff:

-        with open(str(icon), "rb") as icon_file:  # noqa: PTH123
+        with open(str(icon), "rb") as icon_file:
seed/catalog/product.py (1)

50-65: Init path looks solid; consider small lint/idiomatic cleanups

The create-or-return logic and context caching look correct. Two minor improvements:

  • Line 53: the # noqa: PTH123 is now unused; you can both drop it and use the more idiomatic Path.open:
-        logger.debug("Creating product ...")
-        with open(str(icon), "rb") as icon_file:  # noqa: PTH123
+        logger.debug("Creating product ...")
+        with icon.open("rb") as icon_file:
             created = await mpt_vendor.catalog.products.create(
                 {"name": "E2E Seeded", "website": "https://www.example.com"}, file=icon_file
             )
  • Lines 62–63: you already log "Product creation failed" just before raising; to satisfy TRY003 and avoid duplicating the message, you can raise a bare ValueError:
-        logger.warning("Product creation failed")
-        raise ValueError("Product creation failed")
+        logger.warning("Product creation failed")
+        raise ValueError
tests/seed/catalog/test_product.py (4)

43-55: Tidy up unused noqa and consider avoiding hard‑coded icon path

The create-new-product test looks good and uses fs + AsyncMock in a straightforward way. Two minor cleanups:

  • Line 43: # noqa: WPS211 is now reported as unused; it can be safely removed:
-async def test_get_or_create_product_create_new(  # noqa: WPS211
+async def test_get_or_create_product_create_new(
  • Lines 48–50: to make the test less brittle to path changes, consider deriving the icon path from the module instead of hard‑coding the string. For example:
from seed.catalog import product as product_module

fs.create_file(str(product_module.icon), contents=b"fake_icon_bytes")

so the test follows whatever path seed.catalog.product actually uses.


70-75: Optional: assert that review is not called for non‑draft statuses

The non‑draft test currently only checks that review_product() returns the original product. For extra safety and clearer intent, consider also asserting that the review API is not invoked (e.g., via a patched products_service.review.assert_not_called()), so regressions that unnecessarily hit the API for non‑draft products are caught.


77-83: Strengthen publish assertion with argument check

This test correctly verifies that a "Reviewing" product gets published and that publish is called once. To make it more intention‑revealing, you could also assert the call arguments:

operations_client.catalog.products.publish.assert_called_once_with(product.id)

This ensures we don’t accidentally change the call signature without tests failing.


86-90: Optional: ensure publish is skipped for non‑reviewing statuses

Similar to the non‑draft review test, this only checks that publish_product() returns the original product. If you want stronger guarantees, consider wiring in an operations_client (or appropriate mock) here and asserting that publish is never called when status is not "Reviewing".

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b9f708 and def7b8f.

⛔ Files ignored due to path filters (3)
  • seed/data/logo.png is excluded by !**/*.png
  • tests/data/logo.png is excluded by !**/*.png
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (29)
  • mpt_api_client/resources/commerce/assets.py (2 hunks)
  • mpt_api_client/resources/commerce/commerce.py (3 hunks)
  • pyproject.toml (3 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (2 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/e2e/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/test_product.py (3 hunks)
  • tests/seed/conftest.py (3 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
  • tests/unit/resources/commerce/test_assets.py (1 hunks)
  • tests/unit/resources/commerce/test_commerce.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • pyproject.toml
  • tests/e2e/accounts/licensees/conftest.py
  • tests/seed/test_seed_api.py
  • seed/container.py
  • tests/seed/accounts/test_seller.py
  • seed/accounts/seller.py
  • tests/e2e/accounts/sellers/conftest.py
  • tests/seed/accounts/test_api_token.py
  • seed/seed_api.py
  • tests/e2e/accounts/buyers/conftest.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-27T00:05:36.356Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/conftest.py:0-0
Timestamp: 2025-11-27T00:05:36.356Z
Learning: When reviewing PRs that add fixtures to a parent conftest that may duplicate fixtures in child conftest files, do not suggest removing the duplicates from child conftest files that are outside the scope of the PR's changes.

Applied to files:

  • tests/e2e/conftest.py
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/conftest.py
  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (14)
tests/unit/resources/commerce/test_commerce.py (2)
mpt_api_client/resources/commerce/commerce.py (2)
  • assets (33-35)
  • assets (60-62)
mpt_api_client/resources/commerce/assets.py (2)
  • AssetService (32-63)
  • AsyncAssetService (66-100)
tests/unit/resources/commerce/test_assets.py (3)
mpt_api_client/resources/commerce/commerce.py (2)
  • assets (33-35)
  • assets (60-62)
mpt_api_client/resources/commerce/assets.py (2)
  • AssetService (32-63)
  • AsyncAssetService (66-100)
tests/unit/conftest.py (2)
  • http_client (17-18)
  • async_http_client (22-23)
seed/accounts/user_group.py (7)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
tests/seed/conftest.py (1)
  • context (15-16)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/e2e/conftest.py (1)
  • logger (63-64)
tests/e2e/accounts/conftest.py (2)
  • user_group_id (30-31)
  • module_id (35-36)
tests/seed/accounts/test_user_group.py (1)
  • user_group (16-17)
mpt_api_client/resources/commerce/commerce.py (1)
mpt_api_client/resources/commerce/assets.py (2)
  • AssetService (32-63)
  • AsyncAssetService (66-100)
tests/seed/accounts/test_module.py (4)
mpt_api_client/resources/accounts/modules.py (2)
  • AsyncModulesService (29-32)
  • Module (11-12)
seed/accounts/module.py (3)
  • get_module (15-32)
  • refresh_module (36-57)
  • seed_module (61-72)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_user_group.py (4)
mpt_api_client/resources/accounts/user_groups.py (2)
  • AsyncUserGroupsService (32-38)
  • UserGroup (11-12)
seed/accounts/user_group.py (4)
  • build_user_group_data (37-52)
  • get_user_group (16-33)
  • init_user_group (56-76)
  • seed_user_group (80-85)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/accounts/buyer.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/api_tokens.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
seed/catalog/product.py (5)
tests/seed/catalog/test_product.py (1)
  • product (17-18)
tests/seed/conftest.py (1)
  • context (15-16)
mpt_api_client/resources/catalog/products.py (1)
  • Product (53-54)
seed/context.py (1)
  • set_resource (31-33)
mpt_api_client/models/model.py (1)
  • id (119-121)
tests/seed/catalog/test_product.py (3)
tests/seed/conftest.py (3)
  • context (15-16)
  • vendor_client (20-21)
  • operations_client (25-26)
seed/context.py (1)
  • Context (12-33)
seed/catalog/product.py (5)
  • get_product (19-41)
  • init_product (45-65)
  • review_product (69-83)
  • publish_product (87-101)
  • seed_product (104-110)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (90-94)
seed/accounts/licensee.py (1)
  • seed_licensee (101-105)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
tests/seed/accounts/test_buyer.py (2)
seed/accounts/buyer.py (4)
  • build_buyer_data (39-63)
  • get_buyer (18-35)
  • init_buyer (67-86)
  • seed_buyer (90-94)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
tests/seed/accounts/test_licensee.py (3)
seed/accounts/licensee.py (4)
  • build_licensee_data (39-74)
  • get_licensee (18-35)
  • init_licensee (78-97)
  • seed_licensee (101-105)
tests/seed/conftest.py (2)
  • context (15-16)
  • client_client (30-31)
seed/context.py (3)
  • Context (12-33)
  • get_resource (19-29)
  • set_resource (31-33)
🪛 Ruff (0.14.7)
seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/licensee.py

39-39: Unused noqa directive (unknown: WPS238)

Remove unused noqa directive

(RUF100)


45-45: Avoid specifying long messages outside the exception class

(TRY003)


48-48: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)


87-87: Unused noqa directive (non-enabled: PTH123)

Remove unused noqa directive

(RUF100)


95-95: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_user_group.py

41-41: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_accounts.py

6-6: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


46-46: Avoid specifying long messages outside the exception class

(TRY003)


76-76: Unused noqa directive (non-enabled: PTH123)

Remove unused noqa directive

(RUF100)


84-84: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

53-53: Unused noqa directive (non-enabled: PTH123)

Remove unused noqa directive

(RUF100)


63-63: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/catalog/test_product.py

43-43: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_buyer.py

74-74: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_licensee.py

85-85: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)


106-106: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)


107-107: Unused function argument: mocker

(ARG001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (10)
tests/unit/resources/commerce/test_assets.py (1)

6-16: Renamed asset service fixtures correctly aligned with new API

Importing AssetService/AsyncAssetService and using them in the sync/async fixtures matches the updated assets module and keeps the tests consistent with the new public API surface.

tests/unit/resources/commerce/test_commerce.py (1)

6-62: Commerce asset accessors now tested against the renamed service classes

The imports and parametrized expectations for the assets attribute (sync and async) correctly reflect AssetService and AsyncAssetService, matching the updated Commerce/AsyncCommerce implementations.

mpt_api_client/resources/commerce/commerce.py (1)

3-36: Assets properties correctly updated to the renamed services

The sync and async assets properties now import and return AssetService / AsyncAssetService with the existing http_client, which is consistent with the updated assets module and the revised tests.

Also applies to: 59-62

seed/accounts/accounts.py (1)

16-21: LGTM!

The orchestration correctly sequences the seeding in the required dependency order (seller → buyer → module → api_token → user_group → licensee).

tests/seed/accounts/test_buyer.py (1)

86-88: Verify the fake file path matches the actual icon path.

The test creates a file at /mpt_api_client/seed/data/logo.png, but buyer.py resolves the path as pathlib.Path("seed/data/logo.png").resolve(). Depending on the pyfakefs configuration and working directory, this mismatch may cause the test to fail when init_buyer tries to open the icon file.

Please verify the test passes and the fake file path is correctly intercepted by pyfakefs. If not, you may need to create the file at the resolved absolute path or patch the icon variable directly.

seed/catalog/product.py (1)

74-81: Explicitly passing context/vendor into review_product is correct

Using await get_product(context=context, mpt_vendor=mpt_vendor) here keeps the behavior consistent with init_product and avoids any divergence between the caller’s context/client and the defaults. No further issues spotted in this block.

tests/seed/catalog/test_product.py (4)

26-35: test_get_product correctly exercises refresh + context cache path

This test nicely covers the case where an ID exists in the context but no cached resource is present, ensuring the code falls back to vendor_client.catalog.products.get and then stores the refreshed Product via set_resource. Assertions on both result and context.get_resource look good.


37-41: test_get_product_without_id cleanly covers the “no id” branch

This test clearly verifies that get_product returns None when no product ID is present in the Context. The behavior and expectations are consistent with the implementation.


59-67: Draft review path is well covered

This test correctly sets the product to "Draft", ensures get_product yields that object, and verifies that review_product calls the underlying products_service.review exactly once and returns the reviewed product. The flow matches the implementation.


93-100: test_seed_product_sequence cleanly verifies orchestration order

Patching init_product, review_product, and publish_product as async mocks and asserting each is called exactly once is a clear, low‑coupling way to verify the seeding pipeline wiring. This looks good and should be robust to internal changes in those helpers.

@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch 2 times, most recently from 98bd303 to 4433c35 Compare December 4, 2025 15:24
@robcsegal robcsegal requested a review from jentyk December 4, 2025 15:28
@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 4433c35 to 7433ffc Compare December 4, 2025 15:35
@robcsegal robcsegal requested a review from jentyk December 4, 2025 15:38
@robcsegal robcsegal force-pushed the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch from 7433ffc to 52f78a0 Compare December 4, 2025 15:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
seed/catalog/product.py (1)

92-92: Propagate context and mpt_operations to get_product.

Similar to the fix applied to init_product (line 50) and review_product (line 74), publish_product should pass its injected dependencies to get_product instead of using defaults.

Apply this diff:

-    product = await get_product()
+    product = await get_product(context=context, mpt_operations=mpt_operations)

Note: get_product expects mpt_vendor parameter, but publish_product receives mpt_operations. Verify the parameter name matches the intended client.

♻️ Duplicate comments (10)
seed/accounts/accounts.py (1)

13-13: Remove unused noqa directive.

Ruff reports the # noqa: WPS217 directive is unused.

Apply this diff:

-async def seed_accounts() -> None:  # noqa: WPS217
+async def seed_accounts() -> None:
tests/seed/accounts/test_accounts.py (1)

4-4: Remove unused noqa directive.

Ruff reports the # noqa: WPS210 directive is unused.

Apply this diff:

-async def test_seed_accounts(mocker):  # noqa: WPS210
+async def test_seed_accounts(mocker):
seed/accounts/module.py (1)

27-31: Add type validation after API fetch.

After fetching from the API at line 28, the code accesses module.id at line 30 without validating that the result is a Module. This could raise an AttributeError if the API returns an unexpected type.

Apply this diff:

     if not isinstance(module, Module):
         module = await mpt_operations.accounts.modules.get(module_id)
+        if not isinstance(module, Module):
+            logger.warning("API returned non-Module type for id: %s", module_id)
+            return None
         context.set_resource("accounts.module", module)
         context["accounts.module.id"] = module.id
         return module
seed/accounts/api_tokens.py (2)

27-31: Add type validation after API fetch.

After fetching from the API at line 28, the code accesses api_token.id at line 30 without validating that the result is an ApiToken. This could raise an AttributeError if the API returns an unexpected type.

Apply this diff:

     if not isinstance(api_token, ApiToken):
         api_token = await mpt_ops.accounts.api_tokens.get(api_token_id)
+        if not isinstance(api_token, ApiToken):
+            logger.warning("API returned non-ApiToken type for id: %s", api_token_id)
+            return None
         context.set_resource("accounts.api_token", api_token)
         context["accounts.api_token.id"] = api_token.id
         return api_token

40-41: Validate CLIENT_ACCOUNT_ID and module id before building payload.

The function will construct a payload even if CLIENT_ACCOUNT_ID is not set (line 40 returns None) or if accounts.module.id is missing from context (line 41 returns empty string). This can lead to confusing API errors or tokens with invalid ids.

Apply this diff:

 def build_api_token_data(
     context: Context = DEFAULT_CONTEXT,
 ) -> dict[str, object]:
     """Get API token data dictionary for creation."""
     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     module_id = context.get_string("accounts.module.id")
+    if not account_id:
+        raise ValueError("CLIENT_ACCOUNT_ID environment variable is not set.")
+    if not module_id:
+        raise ValueError("accounts.module.id missing from context; seed module first.")
     return {
         "account": {"id": account_id},
seed/accounts/licensee.py (2)

30-34: Add type validation after API fetch.

After fetching via mpt_client.accounts.licensees.get(licensee_id), the code accesses licensee.id without confirming the result is a Licensee. This is inconsistent with the defensive pattern used in init_licensee (lines 89-95).

     if not isinstance(licensee, Licensee):
         licensee = await mpt_client.accounts.licensees.get(licensee_id)
+        if not isinstance(licensee, Licensee):
+            logger.warning("Unexpected type returned for licensee id %s", licensee_id)
+            return None
         context.set_resource("accounts.licensee", licensee)
         context["accounts.licensee.id"] = licensee.id
         return licensee

52-54: Unreachable None check: get_resource raises, never returns None.

context.get_resource("accounts.user_group") either returns a Model or raises ValueError—it never returns None. The if group is None check is unreachable. Wrap the call in try/except to catch the ValueError and provide a clearer error message about seeding order.

-    group = context.get_resource("accounts.user_group")
-    if group is None:
-        raise ValueError("User group is required in context")
+    try:
+        group = context.get_resource("accounts.user_group")
+    except ValueError as exc:
+        raise ValueError(
+            "accounts.user_group missing from context; seed user group before licensee."
+        ) from exc
seed/accounts/user_group.py (2)

44-52: Add validation for module_id before building user group payload.

context.get_string("accounts.module.id") returns an empty string if the key is missing (default is ""). This empty value would be used in the payload as "modules": [{"id": ""}], causing an API error. Add validation matching the pattern used for account_id.

     account_id = os.getenv("CLIENT_ACCOUNT_ID")
     if not account_id:
         raise ValueError("CLIENT_ACCOUNT_ID environment variable is required")
     module_id = context.get_string("accounts.module.id")
+    if not module_id:
+        raise ValueError(
+            "accounts.module.id missing from context; seed module before user group."
+        )
     return {

28-32: Add type validation after API fetch.

After fetching via mpt_operations.accounts.user_groups.get(user_group_id), the code accesses user_group.id without confirming the result is a UserGroup. This is inconsistent with the defensive pattern used in init_user_group (lines 69-76).

     if not isinstance(user_group, UserGroup):
         user_group = await mpt_operations.accounts.user_groups.get(user_group_id)
+        if not isinstance(user_group, UserGroup):
+            logger.warning("Unexpected type returned for user_group id %s", user_group_id)
+            return None
         context.set_resource("accounts.user_group", user_group)
         context["accounts.user_group.id"] = user_group.id
         return user_group
seed/accounts/buyer.py (1)

30-34: Add type validation after API fetch.

After fetching via mpt_operations.accounts.buyers.get(buyer_id), the code accesses buyer.id without confirming the result is a Buyer. This is inconsistent with the defensive pattern used in init_buyer (lines 78-84).

     if not isinstance(buyer, Buyer):
         buyer = await mpt_operations.accounts.buyers.get(buyer_id)
+        if not isinstance(buyer, Buyer):
+            logger.warning("Unexpected type returned for buyer id %s", buyer_id)
+            return None
         context.set_resource("accounts.buyer", buyer)
         context["accounts.buyer.id"] = buyer.id
         return buyer
🧹 Nitpick comments (11)
seed/catalog/product.py (1)

53-53: Remove unused noqa directive.

Ruff reports the # noqa: PTH123 directive is unused.

Apply this diff:

-        with open(str(icon), "rb") as icon_file:  # noqa: PTH123
+        with open(str(icon), "rb") as icon_file:
seed/accounts/module.py (1)

69-69: Consider adding noqa for TRY003.

Ruff flags this line for specifying a long message outside the exception class. If the explicit message is important, you can suppress the linter.

Apply this diff:

-            raise ValueError("Could not seed module: no valid Module found.")
+            raise ValueError("Could not seed module: no valid Module found.")  # noqa: TRY003
tests/seed/catalog/test_product.py (1)

41-41: Remove unused noqa directive.

Ruff reports the # noqa: WPS211 directive is unused.

Apply this diff:

-async def test_get_or_create_product_create_new(  # noqa: WPS211
+async def test_get_or_create_product_create_new(
     context: Context, vendor_client, products_service, product, fs, mocker
 ):
tests/seed/accounts/test_buyer.py (1)

72-72: Remove unused noqa directive.

Ruff reports the # noqa: WPS211 directive is unused.

Apply this diff:

-async def test_init_buyer_create_new(  # noqa: WPS211
+async def test_init_buyer_create_new(
     context: Context,
     operations_client,
tests/seed/accounts/test_user_group.py (1)

39-55: Remove unused noqa directive.

The # noqa: WPS211 directive is flagged as unused by Ruff (RUF100). The WPS211 rule is not recognized/enabled.

-async def test_init_user_group(  # noqa: WPS211
+async def test_init_user_group(
     context: Context, operations_client, user_groups_service, user_group, monkeypatch, mocker
 ):
seed/accounts/licensee.py (2)

38-39: Remove unused noqa directive.

The # noqa: WPS238 directive is flagged as unused by Ruff (RUF100).

 @inject
-def build_licensee_data(  # noqa: WPS238
+def build_licensee_data(
     context: Context = DEFAULT_CONTEXT,
 ) -> dict[str, object]:

86-87: Remove unused noqa directive.

The # noqa: PTH123 directive is flagged as unused by Ruff (RUF100).

-        with open(str(icon), "rb") as icon_file:  # noqa: PTH123
+        with open(str(icon), "rb") as icon_file:
seed/accounts/buyer.py (1)

75-76: Remove unused noqa directive.

The # noqa: PTH123 directive is flagged as unused by Ruff (RUF100).

-        with open(str(icon), "rb") as icon_file:  # noqa: PTH123
+        with open(str(icon), "rb") as icon_file:
tests/seed/accounts/test_licensee.py (2)

83-84: Remove unused noqa directive.

The # noqa: WPS211 directive is flagged as unused by Ruff (RUF100).

-async def test_init_licensee(  # noqa: WPS211
+async def test_init_licensee(
     context: Context, client_client, licensees_service, licensee, monkeypatch, mocker, fs
 ):

106-108: Remove unused noqa directive and unused mocker argument.

The # noqa: WPS211 directive is flagged as unused by Ruff (RUF100), and mocker is not used in this test function (ARG001).

-async def test_init_licensee_create_new(  # noqa: WPS211
-    context: Context, client_client, licensees_service, licensee, monkeypatch, fs, mocker
+async def test_init_licensee_create_new(
+    context: Context, client_client, licensees_service, licensee, monkeypatch, fs
 ):
seed/accounts/user_group.py (1)

1-1: Consider removing the mypy suppression if no longer needed.

The # mypy: disable-error-code=unreachable suppression at the module level may be masking legitimate type issues. Since return types are now UserGroup | None, this suppression may no longer be necessary. Verify by removing it and running mypy.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between def7b8f and 52f78a0.

⛔ Files ignored due to path filters (3)
  • seed/data/logo.png is excluded by !**/*.png
  • tests/data/logo.png is excluded by !**/*.png
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (30)
  • mpt_api_client/resources/commerce/assets.py (2 hunks)
  • mpt_api_client/resources/commerce/commerce.py (3 hunks)
  • pyproject.toml (3 hunks)
  • seed/accounts/accounts.py (1 hunks)
  • seed/accounts/api_tokens.py (1 hunks)
  • seed/accounts/buyer.py (1 hunks)
  • seed/accounts/licensee.py (1 hunks)
  • seed/accounts/module.py (1 hunks)
  • seed/accounts/seller.py (1 hunks)
  • seed/accounts/user_group.py (1 hunks)
  • seed/catalog/product.py (2 hunks)
  • seed/container.py (1 hunks)
  • seed/seed_api.py (2 hunks)
  • tests/e2e/accounts/buyers/conftest.py (1 hunks)
  • tests/e2e/accounts/licensees/conftest.py (1 hunks)
  • tests/e2e/accounts/sellers/conftest.py (1 hunks)
  • tests/e2e/conftest.py (1 hunks)
  • tests/seed/accounts/test_accounts.py (1 hunks)
  • tests/seed/accounts/test_api_token.py (1 hunks)
  • tests/seed/accounts/test_buyer.py (1 hunks)
  • tests/seed/accounts/test_licensee.py (1 hunks)
  • tests/seed/accounts/test_module.py (1 hunks)
  • tests/seed/accounts/test_seller.py (1 hunks)
  • tests/seed/accounts/test_user_group.py (1 hunks)
  • tests/seed/catalog/conftest.py (0 hunks)
  • tests/seed/catalog/test_product.py (2 hunks)
  • tests/seed/conftest.py (1 hunks)
  • tests/seed/test_seed_api.py (2 hunks)
  • tests/unit/resources/commerce/test_assets.py (1 hunks)
  • tests/unit/resources/commerce/test_commerce.py (3 hunks)
💤 Files with no reviewable changes (1)
  • tests/seed/catalog/conftest.py
🚧 Files skipped from review as they are similar to previous changes (15)
  • tests/e2e/accounts/buyers/conftest.py
  • tests/e2e/conftest.py
  • tests/seed/accounts/test_api_token.py
  • tests/e2e/accounts/licensees/conftest.py
  • pyproject.toml
  • seed/container.py
  • tests/seed/accounts/test_module.py
  • seed/accounts/seller.py
  • tests/seed/accounts/test_seller.py
  • tests/seed/conftest.py
  • tests/unit/resources/commerce/test_assets.py
  • tests/unit/resources/commerce/test_commerce.py
  • tests/seed/test_seed_api.py
  • mpt_api_client/resources/commerce/assets.py
  • tests/e2e/accounts/sellers/conftest.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T00:05:54.701Z
Learnt from: robcsegal
Repo: softwareone-platform/mpt-api-python-client PR: 131
File: tests/e2e/accounts/accounts_users/test_async_accounts_users.py:0-0
Timestamp: 2025-11-27T00:05:54.701Z
Learning: In pytest-asyncio, async fixtures are automatically resolved before being passed to test functions (both sync and async). Test functions receive the yielded value from async fixtures, not coroutines, so fixture parameters should never be awaited. A sync test function can use async fixtures without any special handling.

Applied to files:

  • tests/seed/catalog/test_product.py
🧬 Code graph analysis (12)
seed/accounts/buyer.py (5)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/buyers.py (1)
  • Buyer (26-27)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_buyer.py (1)
  • buyer (9-10)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/accounts/accounts.py (6)
seed/accounts/api_tokens.py (1)
  • seed_api_token (71-75)
seed/accounts/buyer.py (1)
  • seed_buyer (90-94)
seed/accounts/licensee.py (1)
  • seed_licensee (101-105)
seed/accounts/module.py (1)
  • seed_module (61-72)
seed/accounts/seller.py (1)
  • seed_seller (78-82)
seed/accounts/user_group.py (1)
  • seed_user_group (80-85)
seed/accounts/module.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/modules.py (1)
  • Module (11-12)
mpt_api_client/rql/query_builder.py (1)
  • RQLQuery (115-524)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_user_group.py (2)
seed/accounts/user_group.py (4)
  • build_user_group_data (37-52)
  • get_user_group (16-33)
  • init_user_group (56-76)
  • seed_user_group (80-85)
seed/context.py (2)
  • Context (12-33)
  • get_resource (19-29)
seed/accounts/licensee.py (4)
mpt_api_client/resources/accounts/licensees.py (1)
  • Licensee (21-22)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_licensee.py (1)
  • licensee (10-28)
mpt_api_client/models/model.py (1)
  • id (119-121)
seed/seed_api.py (4)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
seed/context.py (1)
  • load_context (36-52)
tests/seed/conftest.py (1)
  • context (14-15)
seed/catalog/catalog.py (1)
  • seed_catalog (32-39)
seed/accounts/api_tokens.py (3)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/api_tokens.py (1)
  • ApiToken (12-13)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/catalog/test_product.py (1)
seed/catalog/product.py (5)
  • get_product (19-41)
  • init_product (45-65)
  • review_product (69-83)
  • publish_product (87-101)
  • seed_product (104-110)
seed/accounts/user_group.py (4)
mpt_api_client/mpt_client.py (1)
  • AsyncMPTClient (20-71)
mpt_api_client/resources/accounts/user_groups.py (1)
  • UserGroup (11-12)
tests/seed/conftest.py (1)
  • context (14-15)
seed/context.py (4)
  • Context (12-33)
  • get_string (15-17)
  • get_resource (19-29)
  • set_resource (31-33)
mpt_api_client/resources/commerce/commerce.py (2)
mpt_api_client/mpt_client.py (2)
  • commerce (49-51)
  • commerce (98-105)
mpt_api_client/resources/commerce/assets.py (2)
  • AssetService (32-63)
  • AsyncAssetService (66-100)
tests/seed/accounts/test_licensee.py (2)
seed/accounts/licensee.py (4)
  • build_licensee_data (39-74)
  • get_licensee (18-35)
  • init_licensee (78-97)
  • seed_licensee (101-105)
seed/context.py (3)
  • Context (12-33)
  • get_resource (19-29)
  • set_resource (31-33)
tests/seed/accounts/test_accounts.py (1)
seed/accounts/accounts.py (1)
  • seed_accounts (13-23)
🪛 Ruff (0.14.7)
seed/accounts/buyer.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)


46-46: Avoid specifying long messages outside the exception class

(TRY003)


76-76: Unused noqa directive (non-enabled: PTH123)

Remove unused noqa directive

(RUF100)


84-84: Avoid specifying long messages outside the exception class

(TRY003)

seed/accounts/accounts.py

13-13: Unused noqa directive (unknown: WPS217)

Remove unused noqa directive

(RUF100)

tests/seed/accounts/test_buyer.py

72-72: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)

seed/accounts/module.py

69-69: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_user_group.py

39-39: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)

seed/accounts/licensee.py

39-39: Unused noqa directive (unknown: WPS238)

Remove unused noqa directive

(RUF100)


45-45: Avoid specifying long messages outside the exception class

(TRY003)


48-48: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)


87-87: Unused noqa directive (non-enabled: PTH123)

Remove unused noqa directive

(RUF100)


95-95: Avoid specifying long messages outside the exception class

(TRY003)

seed/catalog/product.py

53-53: Unused noqa directive (non-enabled: PTH123)

Remove unused noqa directive

(RUF100)


63-63: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/catalog/test_product.py

41-41: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)

seed/accounts/user_group.py

43-43: Avoid specifying long messages outside the exception class

(TRY003)

tests/seed/accounts/test_licensee.py

83-83: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)


106-106: Unused noqa directive (unknown: WPS211)

Remove unused noqa directive

(RUF100)


107-107: Unused function argument: mocker

(ARG001)

tests/seed/accounts/test_accounts.py

4-4: Unused noqa directive (unknown: WPS210)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (18)
mpt_api_client/resources/commerce/commerce.py (3)

3-3: Import correctly updated to match renamed classes.

The import aligns with the AssetService and AsyncAssetService class definitions in assets.py.


32-35: LGTM!

The property correctly reflects the renamed AssetService class.


59-62: LGTM!

The async property correctly mirrors the sync version with the renamed AsyncAssetService class.

seed/catalog/product.py (1)

50-51: LGTM! Context propagation fixed.

The previous issue with init_product ignoring its context and mpt_vendor parameters has been resolved. The parameters are now correctly passed to get_product.

tests/seed/catalog/test_product.py (1)

20-21: LGTM! Test modernization complete.

The tests have been successfully updated to use the mocker fixture and pyfakefs (fs fixture), addressing previous review feedback. The consistent use of mocker.patch, mocker.AsyncMock, and fs.create_file improves test reliability and maintainability.

Also applies to: 46-52

seed/accounts/api_tokens.py (1)

57-60: LGTM! Context propagation fixed.

The previous issue with init_api_token ignoring its injected context and mpt_ops parameters has been resolved. Both get_api_token and build_api_token_data now receive the correct dependencies.

seed/accounts/accounts.py (1)

16-21: LGTM! Clear orchestration pattern.

The sequential seeding flow is well-structured and follows a clear pattern. The ordering ensures dependencies are seeded before dependent entities.

tests/seed/accounts/test_accounts.py (1)

5-33: LGTM! Proper use of mocker fixture.

The test correctly uses the mocker fixture with AsyncMock for all async function patches, and validates that each seeder is called exactly once. This addresses previous review feedback.

seed/seed_api.py (1)

20-26: LGTM! Concurrent seeding implementation.

The refactor to run catalog and accounts seeding concurrently is well-implemented. The explicit type annotation and separate task creation improve code clarity.

tests/seed/accounts/test_buyer.py (1)

65-66: LGTM! Proper use of test fixtures.

The tests correctly use the mocker fixture, monkeypatch for environment variables, and pyfakefs (fs) for file system operations. This addresses previous review feedback about using pytest fixtures consistently.

Also applies to: 82-86, 92-93

tests/seed/accounts/test_user_group.py (2)

23-31: LGTM!

The test correctly verifies that get_user_group fetches from the API when the ID is in context and properly stores the result in the context resource store.


58-72: LGTM!

The test properly sets the CLIENT_ACCOUNT_ID environment variable via monkeypatch and validates the payload structure, addressing the previously flagged issue.

seed/accounts/licensee.py (1)

77-97: LGTM!

The init_licensee function properly validates the creation result with isinstance(created, Licensee) before accessing its properties, following defensive programming patterns.

seed/accounts/buyer.py (2)

38-63: LGTM!

The build_buyer_data function properly validates both CLIENT_ACCOUNT_ID environment variable and seller_id from context before building the payload, following the established pattern.


66-86: LGTM!

The init_buyer function correctly forwards dependencies to helper functions and validates the creation result with isinstance(created, Buyer).

tests/seed/accounts/test_licensee.py (2)

52-80: LGTM!

The test_build_licensee_data test properly sets up the environment variable and all required context values (seller, buyer, user_group), matching the validation requirements in build_licensee_data.


122-127: LGTM!

The test_seed_licensee test correctly uses mocker.AsyncMock to patch init_licensee and verifies it was awaited.

seed/accounts/user_group.py (1)

55-76: LGTM!

The init_user_group function properly validates the creation result with isinstance(created, UserGroup) and correctly returns None on failure, which aligns with the UserGroup | None return type annotation.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 4, 2025

@robcsegal robcsegal merged commit 0637ffc into main Dec 4, 2025
6 checks passed
@robcsegal robcsegal deleted the MPT-15443-seed-data-required-for-accounts-e-2-e-tests branch December 4, 2025 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants