From b4664f8377352d6c3b159d4bab43093c24a82fd8 Mon Sep 17 00:00:00 2001 From: Matt Hughes Date: Mon, 25 Feb 2019 15:58:11 -0500 Subject: [PATCH] Add IDV bypass mechanism for bok_choy tests An older test was deleted based on flakiness around the ID verification process; this test eliminates the dependency on IDV by enabling manual ID verification (an enterprise-motivated workaround for IDV requirements) via the auto_auth endpoint. JIRA:EDUCATOR-1178 --- .../test/acceptance/pages/common/auto_auth.py | 10 +++- common/test/acceptance/tests/helpers.py | 4 +- .../tests/lms/test_lms_courseware.py | 55 +++++++++++-------- .../djangoapps/user_authn/views/auto_auth.py | 9 +++ 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/common/test/acceptance/pages/common/auto_auth.py b/common/test/acceptance/pages/common/auto_auth.py index f35e9c43f4..5ccfd8e7f3 100644 --- a/common/test/acceptance/pages/common/auto_auth.py +++ b/common/test/acceptance/pages/common/auto_auth.py @@ -24,8 +24,9 @@ class AutoAuthPage(PageObject): # Internal cache for parsed user info. _user_info = None - def __init__(self, browser, username=None, email=None, password=None, full_name=FULL_NAME, staff=False, superuser=None, - course_id=None, enrollment_mode=None, roles=None, no_login=False, is_active=True, course_access_roles=None): + def __init__(self, browser, username=None, email=None, password=None, full_name=FULL_NAME, staff=False, + superuser=None, course_id=None, enrollment_mode=None, roles=None, no_login=False, is_active=True, + course_access_roles=None, should_manually_verify=False): """ Auto-auth is an end-point for HTTP GET requests. By default, it will create accounts with random user credentials, @@ -37,6 +38,8 @@ class AutoAuthPage(PageObject): `superuser` is a boolean indicating whether the user is a super user. `course_id` is the ID of the course to enroll the student in. Currently, this has the form "org/number/run" + `should_manually_verify` is a boolean indicating whether the + created user should have their identification verified Note that "global staff" is NOT the same as course staff. """ @@ -80,6 +83,9 @@ class AutoAuthPage(PageObject): if no_login: self._params['no_login'] = True + if should_manually_verify: + self._params['should_manually_verify'] = True + @property def url(self): """ diff --git a/common/test/acceptance/tests/helpers.py b/common/test/acceptance/tests/helpers.py index e95b4521b5..4a565efc95 100644 --- a/common/test/acceptance/tests/helpers.py +++ b/common/test/acceptance/tests/helpers.py @@ -402,11 +402,11 @@ def create_multiple_choice_problem(problem_name): ) -def auto_auth(browser, username, email, staff, course_id): +def auto_auth(browser, username, email, staff, course_id, **kwargs): """ Logout and login with given credentials. """ - AutoAuthPage(browser, username=username, email=email, course_id=course_id, staff=staff).visit() + AutoAuthPage(browser, username=username, email=email, course_id=course_id, staff=staff, **kwargs).visit() def assert_link(test, expected_link, actual_link): diff --git a/common/test/acceptance/tests/lms/test_lms_courseware.py b/common/test/acceptance/tests/lms/test_lms_courseware.py index 2fc8aa0e8b..b36d80f216 100644 --- a/common/test/acceptance/tests/lms/test_lms_courseware.py +++ b/common/test/acceptance/tests/lms/test_lms_courseware.py @@ -186,7 +186,14 @@ class ProctoredExamTest(UniqueCourseTest): login as a verififed user """ - auto_auth(self.browser, self.USERNAME, self.EMAIL, False, self.course_id) + auto_auth( + self.browser, + self.USERNAME, + self.EMAIL, + False, + self.course_id, + should_manually_verify=True + ) # the track selection page cannot be visited. see the other tests to see if any prereq is there. # Navigate to the track selection page @@ -201,28 +208,6 @@ class ProctoredExamTest(UniqueCourseTest): # Submit payment self.fake_payment_page.submit_payment() - def _verify_user(self): - """ - Takes user through the verification flow and then marks the verification as 'approved'. - """ - # Immediately verify the user - self.immediate_verification_page.immediate_verification() - - # Take face photo and proceed to the ID photo step - self.payment_and_verification_flow.webcam_capture() - self.payment_and_verification_flow.next_verification_step(self.immediate_verification_page) - - # Take ID photo and proceed to the review photos step - self.payment_and_verification_flow.webcam_capture() - self.payment_and_verification_flow.next_verification_step(self.immediate_verification_page) - - # Submit photos and proceed to the enrollment confirmation step - self.payment_and_verification_flow.next_verification_step(self.immediate_verification_page) - - # Mark the verification as passing. - verification = FakeSoftwareSecureVerificationPage(self.browser).visit() - verification.mark_approved() - def test_can_create_proctored_exam_in_studio(self): """ Given that I am a staff member @@ -237,6 +222,30 @@ class ProctoredExamTest(UniqueCourseTest): self.studio_course_outline.open_subsection_settings_dialog() self.assertTrue(self.studio_course_outline.proctoring_items_are_displayed()) + def test_proctored_exam_flow(self): + """ + Given that I am a staff member on the exam settings section + select advanced settings tab + When I Make the exam proctored. + And I login as a verified student. + And I verify the user's ID. + And visit the courseware as a verified student. + Then I can see an option to take the exam as a proctored exam. + """ + LogoutPage(self.browser).visit() + auto_auth(self.browser, "STAFF_TESTER", "staff101@example.com", True, self.course_id) + self.studio_course_outline.visit() + self.studio_course_outline.open_subsection_settings_dialog() + + self.studio_course_outline.select_advanced_tab() + self.studio_course_outline.make_exam_proctored() + + LogoutPage(self.browser).visit() + self._login_as_a_verified_user() + + self.courseware_page.visit() + self.assertTrue(self.courseware_page.can_start_proctored_exam) + def _setup_and_take_timed_exam(self, hide_after_due=False): """ Helper to perform the common action "set up a timed exam as staff, diff --git a/openedx/core/djangoapps/user_authn/views/auto_auth.py b/openedx/core/djangoapps/user_authn/views/auto_auth.py index 3ea5f719a6..69ba2d6775 100644 --- a/openedx/core/djangoapps/user_authn/views/auto_auth.py +++ b/openedx/core/djangoapps/user_authn/views/auto_auth.py @@ -14,6 +14,7 @@ from django.template.context_processors import csrf from django.utils.translation import ugettext as _ from django_comment_common.models import assign_role +from lms.djangoapps.verify_student.models import ManualVerification from opaque_keys.edx.locator import CourseLocator from openedx.core.djangoapps.user_api.accounts.utils import generate_password from openedx.features.course_experience import course_home_url_name @@ -52,6 +53,7 @@ def auto_auth(request): # pylint: disable=too-many-statements course home page if course_id is defined, otherwise it will redirect to dashboard * `redirect_to`: will redirect to to this url * `is_active` : make/update account with status provided as 'is_active' + * `should_manually_verify`: Whether the created user should have their identification verified If username, email, or password are not provided, use randomly generated credentials. """ @@ -74,6 +76,10 @@ def auto_auth(request): # pylint: disable=too-many-statements # Valid modes: audit, credit, honor, no-id-professional, professional, verified enrollment_mode = request.GET.get('enrollment_mode', 'honor') + # Whether to add a manual ID verification record for the user (can + # be helpful for bypassing certain gated features) + should_manually_verify = _str2bool(request.GET.get('should_manually_verify', False)) + # Parse roles, stripping whitespace, and filtering out empty strings roles = _clean_roles(request.GET.get('roles', '').split(',')) course_access_roles = _clean_roles(request.GET.get('course_access_roles', '').split(',')) @@ -122,6 +128,9 @@ def auto_auth(request): # pylint: disable=too-many-statements reg.activate() reg.save() + if should_manually_verify: + ManualVerification.objects.get_or_create(user=user, status="approved") + # ensure parental consent threshold is met year = datetime.date.today().year age_limit = settings.PARENTAL_CONSENT_AGE_LIMIT