-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add base and learner portal bff handler #581
base: main
Are you sure you want to change the base?
Conversation
b0a595d
to
5c8aff2
Compare
5c8aff2
to
9ff437f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great, left a couple suggestions to consider and a few nits.
context (HandlerContext): The context object containing request information and data. | ||
""" | ||
super().__init__(context) | ||
self.context = context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this line still isn't required, as the BaseHandler
already sets self.context
in the previous super().__init__(context)
line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which line did you remove? It looks like you removed super().__init__(context)
, not self.context = context
?
if not (subscriptions := self.context.data['subscriptions']): | ||
self.load_subscription_licenses() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[suggestion] I'm not quite sure if we truly need to conditionally call self.load_subscription_licenses
here vs. assuming we've already loaded subscription licenses metadata prior to calling get_subscription_licenses
. Given get_subscription_licenses
and get_subscription_licenses_by_status
are only executed via self.process_subscription_licenses
, perhaps an improvement would be to instead have some validation within self.process_subscription_licenses
to raise an exception and/or log if self.context.data['subscriptions']
is not set (i.e., fail early before attempting to process licenses, whereby these methods wouldn't be called).
The potential concern is that it's setting up a pattern where we need to have a side effect of load_subscription_licenses
anytime we need to retrieve data about subscriptions (e.g., having to do this in 2+ places, not super DRY) vs. enforcing these methods don't get called and/or raising an exception within self.process_subscription_licenses
when subscriptions
doesn't yet exist in the context data.
""" | ||
if not (subscriptions := self.context.data['subscriptions']): | ||
self.load_subscription_licenses() | ||
return subscriptions.get('subscription_licenses', []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As is, in the case where self.load_subscription_licenses
is called, I don't believe this line would properly have the updated context data. For example, if we wanted to have the side effect of load_subscription_licenses
, would it need to be something like the following:
if not (subscriptions := self.context.data['subscriptions']):
self.load_subscription_licenses()
# Retrieve subscriptions data from the context again, now that it's been updated via `load_subscription_licenses`.
subscriptions = self.context.data['subscriptions']
return subscriptions.get('subscription_licenses', [])
if not (subscriptions := self.context.data['subscriptions']): | ||
self.load_subscription_licenses() | ||
return subscriptions.get('subscription_licenses_by_status', {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar feedback as get_subscription_licenses
.
# Check if there are 'assigned' licenses that need to be activated | ||
self.check_and_activate_assigned_license() | ||
|
||
# Check if there user should be auto-applied a license |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
# Check if there user should be auto-applied a license | |
# Check if the user should be auto-applied a license |
} | ||
) | ||
@ddt.unpack | ||
def test_handler_context_enterprise_customer_uuid(self, query_params, data): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: perhaps rename slightly to suggest testing parsing params.
def test_handler_context_enterprise_customer_uuid(self, query_params, data): | |
def test_handler_context_enterprise_customer_uuid_param(self, query_params, data): |
request = self.factory.get('sample/api/call') | ||
request.user = self.mock_user | ||
context = HandlerContext(request) | ||
context = HandlerContext(self.request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit/curious] Would it make sense to rely on self.context
here as it's already instantiated in the setUp
?
self.assertEqual(self.context.request, self.request)
# ...
self.request.query_params = { | ||
'enterprise_customer_uuid': self.mock_enterprise_customer_uuid | ||
} | ||
self.request.data = { | ||
'enterprise_customer_uuid': self.mock_enterprise_customer_uuid | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'm not sure if it makes sense to initialize the mock request with both query_params
AND data
simultaneously, as typically it'd be either one or the other.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct but there is a test that checks for whether the query params or data is included. Selectively removing them from the request from setUp
makes for a cleaner test versus either not defining them at all and having to explicitly define it per test, or leaving one of them, and parsing which one exist and which one does not.
@ddt.data(
{
'query_params': True,
'data': True,
},
{
'query_params': False,
'data': True,
},
{
'query_params': True,
'data': False,
},
{
'query_params': False,
'data': False,
}
)
@ddt.unpack
def test_handler_context_enterprise_customer_uuid(self, query_params, data):
if not query_params:
del self.request.query_params['enterprise_customer_uuid']
if not data:
del self.request.data['enterprise_customer_uuid']
request = self.request
if not (query_params or data):
with self.assertRaises(ValueError):
HandlerContext(request)
else:
context = HandlerContext(request)
self.assertEqual(context.enterprise_customer_uuid, self.mock_enterprise_customer_uuid)
self.assertEqual(context.lms_user_id, self.mock_user.lms_user_id)
class TestBaseLearnerPortalHandler(TestBaseHandlerMixin): | ||
def setUp(self): | ||
super().setUp() | ||
self.base_learner_portal_handler = BaseLearnerPortalHandler(self.context) | ||
|
||
# TODO: Test pertaining to currently stubbed out functions deferred for future tickets | ||
|
||
|
||
class TestDashboardHandler(TestBaseHandlerMixin): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these extending a TestBaseHandlerMixin
came out nicely :)
9ff437f
to
f865c22
Compare
Description:
Adds a
BaseHandler
which utilizes theHandlerContext
responsible for the underlying structure for users of theBaseHandler
.Adds the
BaseLearnerPortalHandler
defines respective usable clients and loads data within theHandlerContext
It is the orchaestration layer for the MFE responsible for making the API calls and performing transformations. Currently the functions in this class are stubbed out to be completed in future work.
Adds the
DashboardHandler
which retrieves the data from the context to be consumed by the to be addedResponseBuilder
Adds MVP tests. The functions and tests will be iterated on specifically on within their separate pieces of work.
Jira:
ENT-XXXX
Merge checklist:
./manage.py makemigrations
has been runPost merge: