From 7cf12d861c28310b13f2e94d9e63aa45e3bef4b6 Mon Sep 17 00:00:00 2001 From: Muhammad Adeel Tajamul <77053848+muhammadadeeltajamul@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:56:58 +0500 Subject: [PATCH 1/4] chore: removed style from email digest content (#35592) --- .../rest_api/discussions_notifications.py | 13 +++-- .../tests/test_discussions_notifications.py | 50 ++++++++++--------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/lms/djangoapps/discussion/rest_api/discussions_notifications.py b/lms/djangoapps/discussion/rest_api/discussions_notifications.py index 4e372280ce..498a05fdb9 100644 --- a/lms/djangoapps/discussion/rest_api/discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/discussions_notifications.py @@ -392,7 +392,8 @@ def clean_thread_html_body(html_body): "video", "track", # Video Tags "audio", # Audio Tags "embed", "object", "iframe", # Embedded Content - "script" + "script", + "b", "strong", "i", "em", "u", "s", "strike", "del", "ins", "mark", "sub", "sup", # Text Formatting ] # Remove the specified tags while keeping their content @@ -403,9 +404,10 @@ def clean_thread_html_body(html_body): # Replace tags that are not allowed in email tags_to_update = [ {"source": "button", "target": "span"}, - {"source": "h1", "target": "h4"}, - {"source": "h2", "target": "h4"}, - {"source": "h3", "target": "h4"}, + *[ + {"source": tag, "target": "p"} + for tag in ["div", "section", "article", "h1", "h2", "h3", "h4", "h5", "h6"] + ], ] for tag_dict in tags_to_update: for source_tag in html_body.find_all(tag_dict['source']): @@ -414,4 +416,7 @@ def clean_thread_html_body(html_body): target_tag.string = source_tag.string source_tag.replace_with(target_tag) + for tag in html_body.find_all(True): + tag.attrs = {} + tag['style'] = 'margin: 0' return str(html_body) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py index d92e1000fe..0a8d750416 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py @@ -104,14 +104,14 @@ class TestCleanThreadHtmlBody(unittest.TestCase):

This is a link to a page.

Here is an image: image

Embedded video:

-

Script test:

+

Script test:

Some other content that should remain.

""" - expected_output = ("

This is a link to a page.

" - "

Here is an image:

" - "

Embedded video:

" - "

Script test: alert('hello');

" - "

Some other content that should remain.

") + expected_output = ('

This is a link to a page.

' + '

Here is an image:

' + '

Embedded video:

' + '

Script test: alert("hello");

' + '

Some other content that should remain.

') result = clean_thread_html_body(html_body) @@ -132,19 +132,16 @@ class TestCleanThreadHtmlBody(unittest.TestCase): """ Test that the clean_thread_html_body function truncates the HTML body to 500 characters """ - html_body = """ -

This is a long text that should be truncated to 500 characters.

- """ * 20 # Repeat to exceed 500 characters - - result = clean_thread_html_body(html_body) - self.assertGreaterEqual(500, len(result)) + html_body = "This is a long text that should be truncated to 500 characters." * 20 + result = clean_thread_html_body(f"

{html_body}

") + self.assertGreaterEqual(525, len(result)) # 500 characters + 25 characters for the HTML tags def test_no_tags_to_remove(self): """ Test that the clean_thread_html_body function does not remove any tags if there are no unwanted tags """ html_body = "

This paragraph has no tags to remove.

" - expected_output = "

This paragraph has no tags to remove.

" + expected_output = '

This paragraph has no tags to remove.

' result = clean_thread_html_body(html_body) self.assertEqual(result, expected_output) @@ -169,28 +166,33 @@ class TestCleanThreadHtmlBody(unittest.TestCase): result = clean_thread_html_body(html_body) self.assertEqual(result.strip(), expected_output) + def test_tag_replace(self): + """ + Tests if the clean_thread_html_body function replaces tags + """ + for tag in ["div", "section", "article", "h1", "h2", "h3", "h4", "h5", "h6"]: + html_body = f'<{tag}>Text' + result = clean_thread_html_body(html_body) + self.assertEqual(result, '

Text

') + def test_button_tag_replace(self): """ Tests that the clean_thread_html_body function replaces the button tag with span tag """ # Tests for button replacement tag with text html_body = '' - expected_output = 'Button' + expected_output = 'Button' result = clean_thread_html_body(html_body) self.assertEqual(result, expected_output) # Tests button tag replacement without text html_body = '' - expected_output = '' + expected_output = '' result = clean_thread_html_body(html_body) self.assertEqual(result, expected_output) - def test_heading_tag_replace(self): - """ - Tests that the clean_thread_html_body function replaces the h1, h2 and h3 tags with h4 tag - """ - for tag in ['h1', 'h2', 'h3']: - html_body = f'<{tag}>Heading' - expected_output = '

Heading

' - result = clean_thread_html_body(html_body) - self.assertEqual(result, expected_output) + def test_attributes_removal_from_tag(self): + # Tests for removal of attributes from tags + html_body = '

Paragraph

' + result = clean_thread_html_body(html_body) + self.assertEqual(result, '

Paragraph

') From f5b88392a545c3efcda7015526d0701382b70220 Mon Sep 17 00:00:00 2001 From: Hunia Fatima Date: Wed, 9 Oct 2024 14:44:07 +0500 Subject: [PATCH 2/4] chore: cleanup constraint file and format it (#35601) * chore: cleanup constraint file and format it --- requirements/constraints.txt | 218 +++++++++++++++++++++-------------- 1 file changed, 134 insertions(+), 84 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 3809b753a5..996391b2be 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -7,7 +7,8 @@ # link to other information that will help people in the future to remove the # pin when possible. Writing an issue against the offending project and # linking to it here is good. - +# For further details on how to properly write constraints here please consult +# https://openedx.atlassian.net/wiki/spaces/COMM/pages/4400250883/Adding+pinned+dependencies+in+constraint+file # This file contains all common constraints for edx-repos -c common_constraints.txt @@ -18,127 +19,176 @@ # Ticket: https://github.com/openedx/edx-platform/issues/35334 algoliasearch<4.0.0 +# Date: 2024-03-14 +# Temporary to Support the python 3.11 Upgrade +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35281 +backports.zoneinfo;python_version<"3.9" # Newer versions have zoneinfo available in the standard library + +# Date: 2020-02-26 # As it is not clarified what exact breaking changes will be introduced as per # the next major release, ensure the installed version is within boundaries. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35280 celery>=5.2.2,<6.0.0 +# Date: 2021-05-17 +# greater version breaking upgrade builds +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35279 +click==8.1.6 +# Date: 2022-07-20 +# edx-enterprise, snowflake-connector-python require charset-normalizer==2.0.0 +# Can be removed once snowflake-connector-python>2.7.9 is released with the fix. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35278 +charset-normalizer<2.1.0 + +# Date: 2024-02-02 +# Stay on LTS version, remove once this is added to common constraint +Django<5.0 + +# Date: 2020-02-10 +# django-oauth-toolkit version >=2.0.0 has breaking changes. More details +# mentioned on this issue https://github.com/openedx/edx-platform/issues/32884 +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35277 +django-oauth-toolkit==1.7.1 + +# Date: 2024-02-02 +# incremental upgrade +django-simple-history==3.4.0 + +# Date: 2021-05-17 +# greater version has breaking changes and requires some migration steps. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35276 +django-webpack-loader==0.7.0 + +# Date: 2023-06-20 +# Adding pin to avoid any major upgrade +djangorestframework<3.15.0 + +# Date: 2023-07-19 +# The version of django-stubs we can use depends on which Django release we're using +# 1.16.0 works with Django 3.2 through 4.1 +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35275 +django-stubs==1.16.0 +djangorestframework-stubs==3.14.0 # Pinned to match django-stubs. Remove this when we can remove the above pin. + +# Date: 2024-07-23 +# django-storages==1.14.4 breaks course imports +# Two lines were added in 1.14.4 that make file_exists_in_storage function always return False, +# as the default value of AWS_S3_FILE_OVERWRITE is True +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35170 +django-storages<1.14.4 + +# Date: 2019-08-16 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. edx-enterprise==4.27.0 -# Stay on LTS version, remove once this is added to common constraint -Django<5.0 - -# django-oauth-toolkit version >=2.0.0 has breaking changes. More details -# mentioned on this issue https://github.com/openedx/edx-platform/issues/32884 -django-oauth-toolkit==1.7.1 - -# incremental upgrade -django-simple-history==3.4.0 - -# Adding pin to avoid any major upgrade -pymongo<4.4.1 +# Date: 2024-05-09 +# This has to be constrained as well because newer versions of edx-i18n-tools need the +# newer version of lxml but that requirement was not made expilict in the 1.6.0 version +# of the package. This can be un-pinned when we're upgrading lxml. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35274 +edx-i18n-tools<1.6.0 +# Date: 2024-07-26 # To override the constraint of edx-lint # This can be removed once https://github.com/openedx/edx-platform/issues/34586 is resolved # and the upstream constraint in edx-lint has been removed. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35273 event-tracking==3.0.0 -# greater version has breaking changes and requires some migration steps. -django-webpack-loader==0.7.0 - -# At the time of writing this comment, we do not know whether py2neo>=2022 -# will support our currently-deployed Neo4j version (3.5). -# Feel free to loosen this constraint if/when it is confirmed that a later -# version of py2neo will work with Neo4j 3.5. -py2neo<2022 - -# edx-enterprise, snowflake-connector-python require charset-normalizer==2.0.0 -# Can be removed once snowflake-connector-python>2.7.9 is released with the fix. -charset-normalizer<2.1.0 - -# markdown>=3.4.0 has failures due to internal refactorings which causes the tests to fail -# pinning the version untill the issue gets resolved in the package itself -markdown<3.4.0 - -# pycodestyle==2.9.0 generates false positive error E275. -# Constraint can be removed once the issue https://github.com/PyCQA/pycodestyle/issues/1090 is fixed. -pycodestyle<2.9.0 - -pylint<2.16.0 # greater version failing quality test. Fix them in seperate ticket. - -# urllib3>=2.0.0 conflicts with elastic search && snowflake-connector-python packages -# which require urllib3<2 for now. -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/32222 -urllib3<2.0.0 - - -# Adding pin to avoid any major upgrade -djangorestframework<3.15.0 - -# The version of django-stubs we can use depends on which Django release we're using -# 1.16.0 works with Django 3.2 through 4.1 -django-stubs==1.16.0 -djangorestframework-stubs==3.14.0 # Pinned to match django-stubs. Remove this when we can remove the above pin. - +# Date: 2023-07-26 # Our legacy Sass code is incompatible with anything except this ancient libsass version. # Here is a ticket to upgrade, but it's of debatable importance given that we are rapidly moving # away from legacy LMS/CMS frontends: # https://github.com/openedx/edx-platform/issues/31616 libsass==0.10.0 -# greater version breaking upgrade builds -click==8.1.6 - -# pinning this version to avoid updates while the library is being developed -openedx-learning==0.13.1 - -# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. -openai<=0.28.1 - -# optimizely-sdk 5.0.0 is breaking following test with segmentation fault -# common/djangoapps/third_party_auth/tests/test_views.py::SAMLMetadataTest::test_secure_key_configuration -# needs to be fixed in the follow up issue -# https://github.com/openedx/edx-platform/issues/34103 -optimizely-sdk<5.0 - +# Date: 2024-04-30 # lxml>=5.0 introduced breaking changes related to system dependencies # lxml==5.2.1 introduced new extra so we'll nee to rename lxml --> lxml[html-clean] # This constraint can be removed once we upgrade to Python 3.11 +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35272 lxml<5.0 -# This has to be constrained as well because newer versions of edx-i18n-tools need the -# newer version of lxml but that requirement was not made expilict in the 1.6.0 version -# of the package. This can be un-pinned when we're upgrading lxml. -edx-i18n-tools<1.6.0 -# xmlsec==1.3.14 breaking tests for all builds, can be removed once a fix is available -xmlsec<1.3.14 +# Date: 2018-12-14 +# markdown>=3.4.0 has failures due to internal refactorings which causes the tests to fail +# pinning the version untill the issue gets resolved in the package itself +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35271 +markdown<3.4.0 +# Date: 2024-04-24 # moto==5.0 contains breaking changes. Needs to be updated separately. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35270 moto<5.0 -# path==16.12.0 breaks the unit test collections check -# needs to be investigated and fixed separately -path<16.12.0 - -# Temporary to Support the python 3.11 Upgrade -backports.zoneinfo;python_version<"3.9" # Newer versions have zoneinfo available in the standard library - -# Relevant GitHub Issue: https://github.com/openedx/edx-platform/issues/35126 +# Date: 2024-07-16 # We need to upgrade the version of elasticsearch to atleast 7.15 before we can upgrade to Numpy 2.0.0 # Otherwise we see a failure while running the following command: # export DJANGO_SETTINGS_MODULE=cms.envs.test; python manage.py cms check_reserved_keywords --override_file db_keyword_overrides.yml --report_path reports/reserved_keywords --report_file cms_reserved_keyword_report.csv +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35126 numpy<2.0.0 -# django-storages==1.14.4 breaks course imports -# Two lines were added in 1.14.4 that make file_exists_in_storage function always return False, -# as the default value of AWS_S3_FILE_OVERWRITE is True -django-storages<1.14.4 +# Date: 2024-01-26 +# optimizely-sdk 5.0.0 is breaking following test with segmentation fault +# common/djangoapps/third_party_auth/tests/test_views.py::SAMLMetadataTest::test_secure_key_configuration +# needs to be fixed in the follow up issue +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/34103 +optimizely-sdk<5.0 +# Date: 2023-09-18 +# pinning this version to avoid updates while the library is being developed +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269 +openedx-learning==0.13.1 + +# Date: 2023-11-29 +# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35268 +openai<=0.28.1 + +# Date: 2024-04-26 +# path==16.12.0 breaks the unit test collections check +# needs to be investigated and fixed separately +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35267 +path<16.12.0 + +# Date: 2022-08-03 +# pycodestyle==2.9.0 generates false positive error E275. +# Constraint can be removed once the issue https://github.com/PyCQA/pycodestyle/issues/1090 is fixed. +pycodestyle<2.9.0 + +# Date: 2021-07-12 +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/33560 +pylint<2.16.0 # greater version failing quality test. Fix them in seperate ticket. + +# Date: 2021-08-25 +# At the time of writing this comment, we do not know whether py2neo>=2022 +# will support our currently-deployed Neo4j version (3.5). +# Feel free to loosen this constraint if/when it is confirmed that a later +# version of py2neo will work with Neo4j 3.5. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35266 +py2neo<2022 + +# Date: 2020-04-08 +# Adding pin to avoid any major upgrade +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35265 +pymongo<4.4.1 + +# Date: 2024-08-06 # social-auth-app-django 5.4.2 introduces a new migration that will not play nicely with large installations. This will touch # user tables, which are quite large, especially on instances like edx.org. # We are pinning this until after all the smaller migrations get handled and then we can migrate this all at once. -# Ticket to unpin: https://github.com/edx/edx-arch-experiments/issues/760 +# Issue for unpinning: https://github.com/edx/edx-arch-experiments/issues/760 social-auth-app-django<=5.4.1 + +# Date: 2023-11-05 +# urllib3>=2.0.0 conflicts with elastic search && snowflake-connector-python packages +# which require urllib3<2 for now. +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/32222 +urllib3<2.0.0 + +# Date: 2024-04-24 +# xmlsec==1.3.14 breaking tests or all builds, can be removed once a fix is available +# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35264 +xmlsec<1.3.14 From f1a9286f731f20fde47ca3bdd797b3c615831536 Mon Sep 17 00:00:00 2001 From: Deborah Kaplan Date: Wed, 9 Oct 2024 09:45:05 -0400 Subject: [PATCH 3/4] chore: changing codeowners for several components (#35574) making sure the codeowners for some maintainership is accurate. --- .github/CODEOWNERS | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a05b78e883..fc452f7acd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_task/ lms/djangoapps/mobile_api/ +openedx/core/djangoapps/commerce/ @openedx/2u-infinity openedx/core/djangoapps/credentials @openedx/2U-aperture openedx/core/djangoapps/credit @openedx/2U-aperture openedx/core/djangoapps/enrollments/ @openedx/2U-aperture @@ -22,6 +23,7 @@ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/oauth_dispatch openedx/core/djangoapps/user_api/ @openedx/2U-aperture openedx/core/djangoapps/user_authn/ @openedx/2U-vanguards +openedx/core/djangoapps/verified_track_content/ @openedx/2u-infinity openedx/features/course_experience/ xmodule/ @@ -36,16 +38,18 @@ common/djangoapps/track/ lms/djangoapps/certificates/ @openedx/2U-aperture # Discovery -common/djangoapps/course_modes/ +common/djangoapps/course_modes/ @openedx/2U-aperture common/djangoapps/enrollment/ -lms/djangoapps/branding/ @openedx/2U-aperture -lms/djangoapps/commerce/ -lms/djangoapps/experiments/ @openedx/2U-aperture -lms/djangoapps/learner_dashboard/ @openedx/2U-aperture -lms/djangoapps/learner_home/ @openedx/2U-aperture -openedx/features/content_type_gating/ +common/djangoapps/entitlements/ @openedx/2U-aperture +lms/djangoapps/branding/ @openedx/2U-aperture +lms/djangoapps/commerce/ @openedx/2u-infinity +lms/djangoapps/experiments/ @openedx/2u-infinity +lms/djangoapps/gating/ @openedx/2u-infinity +lms/djangoapps/learner_dashboard/ @openedx/2U-aperture +lms/djangoapps/learner_home/ @openedx/2U-aperture +openedx/features/content_type_gating/ @openedx/2u-infinity openedx/features/course_duration_limits/ -openedx/features/discounts/ +openedx/features/discounts/ @openedx/2u-infinity # Ping Axim On-call if someone uses the QuickStart # https://docs.openedx.org/en/latest/developers/quickstarts/first_openedx_pr.html From 243b1b4e2e6e065b685f678556164d5dcd47eb67 Mon Sep 17 00:00:00 2001 From: Alison Langston <46360176+alangsto@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:16:02 -0400 Subject: [PATCH 4/4] feat: update management command to manually create verified names (#35619) * feat: update management command to manually create verified names * fix: update function name --- .../commands/approve_id_verifications.py | 34 +++++++++++ .../tests/test_approve_id_verifications.py | 59 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py b/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py index 3a08ede0aa..4c45f415cf 100644 --- a/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py @@ -10,9 +10,11 @@ from pprint import pformat from django.core.management.base import BaseCommand, CommandError +from common.djangoapps.student.models_api import get_name, get_pending_name_change from lms.djangoapps.verify_student.api import send_approval_email from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date +from openedx.features.name_affirmation_api.utils import get_name_affirmation_service log = logging.getLogger(__name__) @@ -149,5 +151,37 @@ class Command(BaseCommand): for verification in existing_id_verifications: verification.approve(service='idv_verifications command') send_approval_email(verification) + self._approve_verified_name_for_software_secure_verification(verification) return list(failed_user_ids) + + def _approve_verified_name_for_software_secure_verification(self, verification): + """ + This method manually creates a verified name given a SoftwareSecurePhotoVerification object. + """ + + name_affirmation_service = get_name_affirmation_service() + + if name_affirmation_service: + from edx_name_affirmation.exceptions import VerifiedNameDoesNotExist # pylint: disable=import-error + + pending_name_change = get_pending_name_change(verification.user) + if pending_name_change: + full_name = pending_name_change.new_name + else: + full_name = get_name(verification.user.id) + + try: + name_affirmation_service.update_verified_name_status( + verification.user, + 'approved', + verification_attempt_id=verification.id + ) + except VerifiedNameDoesNotExist: + name_affirmation_service.create_verified_name( + verification.user, + verification.name, + full_name, + verification_attempt_id=verification.id, + status='approved', + ) diff --git a/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py b/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py index e6e580c1d1..6eccee1947 100644 --- a/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py @@ -6,6 +6,8 @@ import ddt import logging import os import tempfile +from unittest import skipUnless +from unittest.mock import MagicMock, patch import pytest from django.core import mail @@ -15,9 +17,12 @@ from testfixtures import LogCapture from common.djangoapps.student.tests.factories import UserFactory, UserProfileFactory from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification +from openedx.features.name_affirmation_api.utils import get_name_affirmation_service LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.approve_id_verifications' +name_affirmation_service = get_name_affirmation_service() + @ddt.ddt class TestApproveIDVerificationsCommand(TestCase): @@ -158,3 +163,57 @@ class TestApproveIDVerificationsCommand(TestCase): """ with pytest.raises(CommandError): call_command('approve_id_verifications', 'invalid/user_id/file/path') + + @skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name_affirmation_service') + def test_create_verified_names(self, mock_get_service): + mock_service = MagicMock() + mock_get_service.return_value = mock_service + + verification = SoftwareSecurePhotoVerification.objects.create( + user=self.user1_profile.user, + name=self.user1_profile.name, + status='submitted', + ) + + call_command('approve_id_verifications', self.tmp_file_path) + mock_service.update_verified_name_status.assert_called_with( + self.user1_profile.user, + 'approved', + verification_attempt_id=verification.id, + ) + + @skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_pending_name_change') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name_affirmation_service') + @ddt.data( + '', + MagicMock(new_name='test') + ) + def test_create_update_verified_names(self, pending_name, mock_get_service, mock_get_pending, mock_get_name): + from edx_name_affirmation.exceptions import VerifiedNameDoesNotExist # pylint: disable=import-error + + mock_service = MagicMock() + mock_get_service.return_value = mock_service + mock_service.update_verified_name_status.side_effect = VerifiedNameDoesNotExist() + + mock_get_pending.return_value = pending_name + mock_get_name.return_value = self.user1_profile.name + + verification = SoftwareSecurePhotoVerification.objects.create( + user=self.user1_profile.user, + name=self.user1_profile.name, + status='submitted', + ) + + expected_name = 'test' if pending_name else self.user1_profile.name + + call_command('approve_id_verifications', self.tmp_file_path) + mock_service.create_verified_name.assert_called_with( + verification.user, + verification.name, + expected_name, + verification_attempt_id=verification.id, + status='approved', + )