Skip to content

MPT-20437: Added endpoints and e2e tests for program enrollments#318

Merged
robcsegal merged 1 commit intomainfrom
MPT-20437-add-endpoints-and-e-2-e-tests-for-program-enrollments
Apr 22, 2026
Merged

MPT-20437: Added endpoints and e2e tests for program enrollments#318
robcsegal merged 1 commit intomainfrom
MPT-20437-add-endpoints-and-e-2-e-tests-for-program-enrollments

Conversation

@robcsegal
Copy link
Copy Markdown
Contributor

@robcsegal robcsegal commented Apr 22, 2026

This pull request adds comprehensive support for program enrollments in the API client, including both synchronous and asynchronous service classes, as well as extensive end-to-end and unit test coverage. It introduces new mixins for rendering resources, updates configuration and test settings, and integrates the enrollments service into the main program module.

Program Enrollment Service Implementation:

  • Added EnrollmentService and AsyncEnrollmentService classes in mpt_api_client/resources/program/enrollments.py, providing CRUD operations and additional actions (validate, query, process, complete, submit, fail) for program enrollments, along with the Enrollment model and service configuration.
  • Introduced RenderMixin and AsyncRenderMixin in mpt_api_client/resources/program/mixins/render_mixin.py to enable rendering resources for both sync and async services.

Integration with Program Module:

  • Registered the new enrollments services (enrollments and async_enrollments properties) in the main program API modules in mpt_api_client/resources/program/program.py. [1] [2] [3]

Testing Enhancements:

  • Added end-to-end tests for both sync and async enrollment flows, covering creation, retrieval, filtering, updating, status transitions, and error handling in tests/e2e/program/enrollment/test_sync_enrollment.py and test_async_enrollment.py. [1] [2]
  • Created test fixtures for enrollment data and status flows in tests/e2e/program/enrollment/conftest.py.
  • Added unit tests for the new render mixins in tests/unit/resources/program/mixin/test_render_mixin.py.

Configuration and Linting Updates:

  • Updated e2e_config.test.json with new enrollment-related IDs for testing.
  • Adjusted linting configuration in pyproject.toml to ignore specific warnings for new enrollment test files.

Closes MPT-20437

  • Added Enrollment model with comprehensive fields (name, certificate, program, vendor, licensee, eligibility, template, audit, status, type, applicable_to, parameters)
  • Implemented EnrollmentService and AsyncEnrollmentService with full CRUD operations and action methods: validate, query, process, complete, submit, fail
  • Introduced RenderMixin and AsyncRenderMixin for rendering resources via GET requests to the render action endpoint
  • Registered enrollments and async_enrollments properties in Program and AsyncProgram classes
  • Added comprehensive E2E test suites for both synchronous and asynchronous enrollment flows, covering creation, retrieval, filtering, updating, status transitions, and error handling
  • Added unit tests for EnrollmentService, AsyncEnrollmentService, and render mixin implementations
  • Updated test configuration with enrollment-related IDs (enrollment.id, assignee.id, and template IDs for process, query, and complete actions)
  • Added pytest fixtures for enrollment E2E tests to support state management and factory functions for payload generation
  • Extended flake8 linting configuration to accommodate new test files

@robcsegal robcsegal requested a review from a team as a code owner April 22, 2026 14:45
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

This PR adds program enrollment functionality to the MPT API client library, introducing an Enrollment resource with service classes supporting state transitions (validate, query, process, complete, submit, fail). It includes render mixin support for resource rendering, integration with the existing Program class, and comprehensive unit and E2E test coverage with test fixtures and configuration.

Changes

Cohort / File(s) Summary
Enrollment Core
mpt_api_client/resources/program/enrollments.py, mpt_api_client/resources/program/mixins/render_mixin.py
New Enrollment model with fields for name, certificate, program, vendor, licensee, eligibility, status, parameters, template, and audit. EnrollmentServiceConfig and paired synchronous/asynchronous EnrollmentService classes with six action methods (validate, query, process, complete, submit, fail) posting to sub-routes. New RenderMixin and AsyncRenderMixin for resource rendering via GET requests.
Program Integration
mpt_api_client/resources/program/program.py
Added enrollments properties to Program and AsyncProgram classes returning EnrollmentService and AsyncEnrollmentService instances respectively.
E2E Test Infrastructure
e2e_config.test.json, pyproject.toml, tests/e2e/program/enrollment/conftest.py
Added enrollment configuration keys (IDs and template references), flake8 per-file ignores for enrollment tests, and pytest fixtures providing config-derived IDs, enrollment data payloads, and factory fixtures for status flows and assignee operations.
E2E Tests
tests/e2e/program/enrollment/test_sync_enrollment.py, tests/e2e/program/enrollment/test_async_enrollment.py
Comprehensive end-to-end test suites (marked flaky) covering enrollment retrieval by ID, pagination, filtering with RQL queries, and full lifecycle operations (create, delete, update, submit, validate, query, process, fail, complete).
Unit Tests
tests/unit/resources/program/test_enrollments.py, tests/unit/resources/program/mixin/test_render_mixin.py, tests/unit/resources/program/test_program.py
Unit tests for Enrollment model serialization, action method POST requests with mocked responses, RenderMixin GET behavior, and Program.enrollments property type assertions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Single Commit Required ❓ Inconclusive Unable to determine the number of commits in this PR due to inaccessible git repository structure and undefined base branch references. Run git log --oneline main..HEAD or git log --oneline master..HEAD to count commits, or check the PR interface for commit count.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Jira Issue Key In Title ✅ Passed The PR title contains exactly one Jira issue key in the required MPT-XXXX format: MPT-20437.
Test Coverage Required ✅ Passed The PR modifies 3 code files and includes 6 comprehensive test files covering both unit and end-to-end tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (3)
tests/e2e/program/enrollment/test_sync_enrollment.py (1)

83-90: Consider asserting the delete post-condition explicitly.

This test currently passes on successful request execution only; adding a get-after-delete assertion makes the behavior check stronger.

Suggested test hardening
 def test_delete_enrollment(mpt_client, created_enrollment):
     enrollment_data = created_enrollment

-    result = mpt_client.program.enrollments
-
-    result.delete(enrollment_data.id)
+    mpt_client.program.enrollments.delete(enrollment_data.id)
+    with pytest.raises(MPTAPIError, match=r"404 Not Found"):
+        mpt_client.program.enrollments.get(enrollment_data.id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/program/enrollment/test_sync_enrollment.py` around lines 83 - 90,
The test_delete_enrollment function only calls
mpt_client.program.enrollments.delete(enrollment_data.id) without verifying the
resource was actually removed; update the test to perform a follow-up retrieval
using mpt_client.program.enrollments.get(enrollment_data.id) and assert the
expected post-delete behavior (e.g., that get raises a 404/NotFound exception or
returns None/False), handling the client’s error/response shape accordingly so
the test fails if the enrollment still exists.
tests/e2e/program/enrollment/test_async_enrollment.py (1)

9-19: Avoid double-delete flow in the delete test and assert the postcondition.

test_delete_enrollment deletes an entity already scheduled for deletion in created_enrollment teardown, and it does not verify that the resource is actually gone. Prefer creating/deleting within the test (or a dedicated no-teardown fixture) and assert a subsequent get() returns 404.

✅ Example tightening of delete verification
-async def test_delete_enrollment(async_mpt_client, created_enrollment):
-    enrollment_data = created_enrollment
-
-    result = async_mpt_client.program.enrollments
-
-    await result.delete(enrollment_data.id)
+async def test_delete_enrollment(async_mpt_client, enrollment_data):
+    enrollment = await async_mpt_client.program.enrollments.create(enrollment_data)
+    await async_mpt_client.program.enrollments.delete(enrollment.id)
+    with pytest.raises(MPTAPIError, match=r"404 Not Found"):
+        await async_mpt_client.program.enrollments.get(enrollment.id)

Based on learnings: "Reuse existing resources for read-only/safe mutations; reserve isolated create/cleanup flows for destructive tests."

Also applies to: 87-94

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/program/enrollment/test_async_enrollment.py` around lines 9 - 19,
The test is double-deleting an enrollment because the created_enrollment fixture
already schedules delete in its teardown; change the destructive test to create
and delete its own enrollment (use async_mpt_client.program.enrollments.create
within test_delete_enrollment) or make a no-teardown fixture variant, then after
calling async_mpt_client.program.enrollments.delete(enrollment.id) assert the
resource is gone by calling
async_mpt_client.program.enrollments.get(enrollment.id) and verifying it raises
MPTAPIError with a 404/NotFound status; update any other destructive test cases
(the ones around the other delete flow) the same way and remove the redundant
deletion from the shared created_enrollment fixture when used by destructive
tests.
mpt_api_client/resources/program/enrollments.py (1)

62-132: Consider a small helper to remove action-method duplication.

validate/query/process/complete/submit/fail are structurally identical in both sync and async services. A helper reduces copy/paste risk and keeps future action additions safer.

♻️ Example refactor
 class EnrollmentService(
@@
 ):
@@
+    def _post_action(
+        self, action: str, resource_id: str, resource_data: ResourceData | None = None
+    ) -> Enrollment:
+        return self._resource(resource_id).post(action, json=resource_data)
+
     def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Enrollment:
@@
-        return self._resource(resource_id).post("validate", json=resource_data)
+        return self._post_action("validate", resource_id, resource_data)
@@
-        return self._resource(resource_id).post("query", json=resource_data)
+        return self._post_action("query", resource_id, resource_data)
@@
-        return self._resource(resource_id).post("process", json=resource_data)
+        return self._post_action("process", resource_id, resource_data)
@@
-        return self._resource(resource_id).post("complete", json=resource_data)
+        return self._post_action("complete", resource_id, resource_data)
@@
-        return self._resource(resource_id).post("submit", json=resource_data)
+        return self._post_action("submit", resource_id, resource_data)
@@
-        return self._resource(resource_id).post("fail", json=resource_data)
+        return self._post_action("fail", resource_id, resource_data)
 class AsyncEnrollmentService(
@@
 ):
@@
+    async def _post_action(
+        self, action: str, resource_id: str, resource_data: ResourceData | None = None
+    ) -> Enrollment:
+        return await self._resource(resource_id).post(action, json=resource_data)
+
@@
-        return await self._resource(resource_id).post("validate", json=resource_data)
+        return await self._post_action("validate", resource_id, resource_data)
@@
-        return await self._resource(resource_id).post("query", json=resource_data)
+        return await self._post_action("query", resource_id, resource_data)
@@
-        return await self._resource(resource_id).post("process", json=resource_data)
+        return await self._post_action("process", resource_id, resource_data)
@@
-        return await self._resource(resource_id).post("complete", json=resource_data)
+        return await self._post_action("complete", resource_id, resource_data)
@@
-        return await self._resource(resource_id).post("submit", json=resource_data)
+        return await self._post_action("submit", resource_id, resource_data)
@@
-        return await self._resource(resource_id).post("fail", json=resource_data)
+        return await self._post_action("fail", resource_id, resource_data)

Also applies to: 144-224

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mpt_api_client/resources/program/enrollments.py` around lines 62 - 132, The
six methods (validate, query, process, complete, submit, fail) duplicate the
same pattern; add a single helper like a private _action method that takes
resource_id, action (string) and resource_data and calls
self._resource(resource_id).post(action, json=resource_data) (and an async
counterpart for the async service that awaits the call), then have each public
method return a call to that helper (e.g., validate -> return
self._action(resource_id, "validate", resource_data)); update both sync and
async classes to use this helper to remove repetition and keep behavior
identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@mpt_api_client/resources/program/enrollments.py`:
- Around line 62-132: The six methods (validate, query, process, complete,
submit, fail) duplicate the same pattern; add a single helper like a private
_action method that takes resource_id, action (string) and resource_data and
calls self._resource(resource_id).post(action, json=resource_data) (and an async
counterpart for the async service that awaits the call), then have each public
method return a call to that helper (e.g., validate -> return
self._action(resource_id, "validate", resource_data)); update both sync and
async classes to use this helper to remove repetition and keep behavior
identical.

In `@tests/e2e/program/enrollment/test_async_enrollment.py`:
- Around line 9-19: The test is double-deleting an enrollment because the
created_enrollment fixture already schedules delete in its teardown; change the
destructive test to create and delete its own enrollment (use
async_mpt_client.program.enrollments.create within test_delete_enrollment) or
make a no-teardown fixture variant, then after calling
async_mpt_client.program.enrollments.delete(enrollment.id) assert the resource
is gone by calling async_mpt_client.program.enrollments.get(enrollment.id) and
verifying it raises MPTAPIError with a 404/NotFound status; update any other
destructive test cases (the ones around the other delete flow) the same way and
remove the redundant deletion from the shared created_enrollment fixture when
used by destructive tests.

In `@tests/e2e/program/enrollment/test_sync_enrollment.py`:
- Around line 83-90: The test_delete_enrollment function only calls
mpt_client.program.enrollments.delete(enrollment_data.id) without verifying the
resource was actually removed; update the test to perform a follow-up retrieval
using mpt_client.program.enrollments.get(enrollment_data.id) and assert the
expected post-delete behavior (e.g., that get raises a 404/NotFound exception or
returns None/False), handling the client’s error/response shape accordingly so
the test fails if the enrollment still exists.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: b54450da-38d1-4af7-ab98-a5cba29b3dc3

📥 Commits

Reviewing files that changed from the base of the PR and between 312c025 and bf486a2.

📒 Files selected for processing (11)
  • e2e_config.test.json
  • mpt_api_client/resources/program/enrollments.py
  • mpt_api_client/resources/program/mixins/render_mixin.py
  • mpt_api_client/resources/program/program.py
  • pyproject.toml
  • tests/e2e/program/enrollment/conftest.py
  • tests/e2e/program/enrollment/test_async_enrollment.py
  • tests/e2e/program/enrollment/test_sync_enrollment.py
  • tests/unit/resources/program/mixin/test_render_mixin.py
  • tests/unit/resources/program/test_enrollments.py
  • tests/unit/resources/program/test_program.py

@robcsegal robcsegal merged commit 28ccd6f into main Apr 22, 2026
4 checks passed
@robcsegal robcsegal deleted the MPT-20437-add-endpoints-and-e-2-e-tests-for-program-enrollments branch April 22, 2026 16:12
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.

2 participants