Skip to content

feat: add EnterpriseEnrollmentViewProcessor pipeline step for CourseEnrollmentStarted filter#2553

Open
pwnage101 wants to merge 7 commits into
masterfrom
pwnage101/ENT-11570
Open

feat: add EnterpriseEnrollmentViewProcessor pipeline step for CourseEnrollmentStarted filter#2553
pwnage101 wants to merge 7 commits into
masterfrom
pwnage101/ENT-11570

Conversation

@pwnage101

@pwnage101 pwnage101 commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Description

An EnterpriseEnrollmentViewProcessor pipeline step is defined in enterprise/filters/enrollment.py for the new openedx-filter CourseEnrollmentViewStarted (here).

The pipeline step checks whether the enrollment user is linked to an enterprise customer, and if so, calls the enterprise and consent API clients to post the enterprise course enrollment and provide consent.

Jira

ENT-11570

Testing instructions

Requires all three branches installed together (follow instructions here)

Use this branch, openedx-filters branch openedx/openedx-filters#363, and edx-platform branch kiram15/ENT-11570

Needs ENABLE_ENTERPRISE_INTEGRATION=True, an API key, an EnterpriseCustomer, and a learner linked to it via EnterpriseCustomerUser.

In the LMS shell, POST to the enrollment endpoint as the worker, patching the two API clients to assert they fire:

from unittest import mock
from django.conf import settings
from rest_framework.test import APIClient

EC_UUID = "<enterprise-uuid>"
CID = "<course-id>"
worker = APIClient(); worker.credentials(HTTP_X_EDX_API_KEY=settings.EDX_API_KEY)

with mock.patch("enterprise.filters.enrollment.EnterpriseApiServiceClient") as MEnt, \
     mock.patch("enterprise.filters.enrollment.ConsentApiServiceClient") as MCon:
    resp = worker.post("/api/enrollment/v1/enrollment",
        {"user": "<username>", "course_details": {"course_id": CID},
         "mode": "audit", "linked_enterprise_customer": EC_UUID}, format="json")
    print(resp.status_code)
    print(MEnt.return_value.post_enterprise_course_enrollment.call_args)
    print(MCon.return_value.provide_consent.call_args)

Expected: 200; the LMS log shows EnterpriseEnrollmentViewProcessor running: …; post_enterprise_course_enrollment is called with (username, course_id); provide_consent is called with username, course_id, and enterprise_customer_uuid.

I also personally tested these other scenarios

  • no linked_enterprise: 200, neither client called
  • no API-key permission: 200, neither client called
  • enterprise post fails: 400, consent not called, no enrollment created
  • consent fails: 400, enterprise called once, no enrollment created

Related:

@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.07%. Comparing base (67cbfdf) to head (6d2c443).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2553      +/-   ##
==========================================
+ Coverage   86.05%   86.07%   +0.02%     
==========================================
  Files         251      252       +1     
  Lines       16718    16744      +26     
  Branches     1658     1659       +1     
==========================================
+ Hits        14387    14413      +26     
  Misses       1997     1997              
  Partials      334      334              
Flag Coverage Δ
unittests 86.07% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kiram15 kiram15 force-pushed the pwnage101/ENT-11570 branch from a35d99d to ceabb63 Compare April 23, 2026 20:39
@kiram15 kiram15 requested a review from Copilot April 23, 2026 20:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an EnterpriseEnrollmentPostProcessor pipeline step to post enterprise enrollment + consent on course enrollment events, with accompanying unit tests.

Changes:

  • Introduces enterprise.filters.enrollment.EnterpriseEnrollmentPostProcessor pipeline step.
  • Adds unit tests covering enterprise vs non-enterprise paths and exception logging behavior.
  • Adds minimal tests/filters package init.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
enterprise/filters/enrollment.py New pipeline step that posts enterprise enrollment and consent, logging failures.
enterprise/filters/init.py Initializes enterprise.filters package.
tests/filters/test_enrollment.py New tests validating API client calls and exception logging.
tests/filters/init.py Initializes tests.filters package.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/filters/test_enrollment.py Outdated
Comment on lines +28 to +39
def _make_openedx_modules():
"""
Build a minimal set of sys.modules entries for the openedx namespace.
"""
entries = {}
for name in (
"openedx",
"openedx.features",
"openedx.features.enterprise_support",
):
entries[name] = ModuleType(name)
return entries

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

These ModuleType stubs won't behave like packages during import because they lack __path__. As a result, from openedx.features.enterprise_support.api import ... can fail with ModuleNotFoundError: 'openedx' is not a package in environments where openedx isn't installed. Fix by marking the namespace modules as packages (e.g., setting __path__ = [] on each) and/or wiring submodules as attributes on their parents so the import machinery can resolve the hierarchy.

Copilot uses AI. Check for mistakes.
Comment thread tests/filters/test_enrollment.py Outdated
Comment thread enterprise/filters/enrollment.py Outdated

@pwnage101 pwnage101 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So, I'm just going to submit this review but before you implement fixes I'd like to meet with you synchronously because there's a lot to unpack and we should discuss an alternate path.

It looks like the CourseEnrollmentStarted filter you were planning to use (possibly adopted from my draft) is installed at the model layer, not the view layer containing the enterprise logic we are trying to replace.

Keep in mind enterprise enrollment creation was added nearly 10 years ago to the view layer, and meanwhile the model layer enroll() method is called from management commands, other views, entitlements, program enrollments, auto-auth, tests, etc. (in all over 80 call sites), so THERE BE DRAGONS if we change this.

Comment thread enterprise/filters/enrollment.py
Comment thread enterprise/filters/enrollment.py Outdated
Comment thread tests/filters/test_enrollment.py Outdated

body = {
'messages': [{'role': role, 'content': prompt},],
'messages': [{'role': role, 'content': prompt}, ],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Had to fix unrelated file that was failing on linter

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The way to fix this is actually to just remove the comma, like this:

Suggested change
'messages': [{'role': role, 'content': prompt}, ],
'messages': [{'role': role, 'content': prompt}],

@kiram15 kiram15 force-pushed the pwnage101/ENT-11570 branch 4 times, most recently from 28745a0 to e92d54e Compare June 7, 2026 22:32
@kiram15 kiram15 force-pushed the pwnage101/ENT-11570 branch from e92d54e to c93b4e3 Compare June 7, 2026 23:18
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kiram15 kiram15 changed the title feat: add EnterpriseEnrollmentPostProcessor pipeline step for CourseEnrollmentStarted filter feat: add EnterpriseEnrollmentViewProcessor pipeline step for CourseEnrollmentStarted filter Jun 8, 2026
Comment thread enterprise/filters/enrollment.py Outdated
assert additions[FILTER_A]['pipeline'] == [STEP_X]


class TestEnterpriseFiltersConfig(unittest.TestCase):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't mind this test being added, but curious why you decided to add it? Seems unrelated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not directly related to the work, more related to the general filter initiative as an smoke test

Comment thread enterprise/filters/enrollment.py Outdated
Comment thread enterprise/filters/enrollment.py Outdated
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