Skip to content

Commit

Permalink
Merge pull request #53 from edx/alangsto/validate_name_change
Browse files Browse the repository at this point in the history
feat: Add name change validator
  • Loading branch information
alangsto authored Sep 15, 2021
2 parents 107d4c7 + c470ad3 commit 72f4224
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Change Log
Unreleased
~~~~~~~~~~

[0.11.0] - 2021-09-15
~~~~~~~~~~~~~~~~~~~~~
* Add name change validator

[0.10.0] - 2021-09-13
~~~~~~~~~~~~~~~~~~~~~
* Add is verified name enabled endpoint
Expand Down
2 changes: 1 addition & 1 deletion edx_name_affirmation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
Django app housing name affirmation logic.
"""

__version__ = '0.10.0'
__version__ = '0.11.0'

default_app_config = 'edx_name_affirmation.apps.EdxNameAffirmationConfig' # pylint: disable=invalid-name
96 changes: 96 additions & 0 deletions edx_name_affirmation/name_change_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Name Change Validator
"""

import re
from difflib import SequenceMatcher


class NameChangeValidator:
"""
Class used to validate name changes
"""

# maximum number of name changes allowed before verification should be triggered
MAX_NUM_NAME_CHANGES = 2

def __init__(self, old_names_list, num_certs, old_name, new_name):
"""
Class initializer
"""
self.old_names_list = old_names_list
self.num_certs = num_certs
self.old_name = old_name
self.new_name = new_name

def _validate_spaces(self):
"""
Validate spaces in a new name
Returns a boolean representing if spaces within string follow current rules
"""
contains_multiple_spaces = bool(re.search(r' {2,}', self.new_name))
if contains_multiple_spaces:
return False
return True

def _validate_string_changes(self):
"""
Validate any changes made from the old name to the new name
Returns a boolean representing if the changes follow current rules
Edits are considered invalid if:
* Two or more spaces occur in a row
* More than one non-space character is added/removed/replaced (exception for if a space
is added on either side of the non-space character)
"""
modifications = 0
# get differences between old name and new name
sequence = SequenceMatcher(lambda x: x == " ", self.old_name, self.new_name)
for tag, i1, i2, j1, j2 in sequence.get_opcodes():
# if there is more than one sequence in the string that has been modified, edits are invalid
if modifications > 1:
return False

# if tag is anything other than equal, increase modifications
if tag != 'equal':
modifications += 1

# determine which piece has been modified
old_name_substring = self.old_name[i1:i2]
new_name_substring = self.new_name[j1:j2]
modified_substring = (
old_name_substring
if len(old_name_substring) > len(new_name_substring)
else new_name_substring
)
is_valid = bool(re.search(r'^\s?(\S\s?)?$', modified_substring))
if not is_valid:
return False

return True

def _validate_old_name_changes(self):
"""
Validate that a user has not changed their name more than the maximum number allowed
"""
return len(self.old_names_list) < self.MAX_NUM_NAME_CHANGES

def _validate_num_certs(self):
"""
Validate that the user does not have any certificates
"""
return self.num_certs == 0

def validate(self):
"""
Return a boolean representing if the edits to a name are valid and follow the current rules for validation
"""
return (
self._validate_num_certs() # if a user has no certs, changes will always be considered valid
or (
self._validate_spaces()
and self._validate_string_changes()
and self._validate_old_name_changes()
)
)
66 changes: 66 additions & 0 deletions edx_name_affirmation/tests/test_name_change_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
Tests for name change validator
"""


import ddt

from django.test import TestCase

from edx_name_affirmation.name_change_validator import NameChangeValidator


@ddt.ddt
class NameChangeValidatorTests(TestCase):
"""
Tests for name_change_validator.py
"""

@ddt.data(
('Jonathan Adams', 'Jon Adams'),
('Jonathan Adams', 'Jonathan Quincy Adams'),
('Jonathan Adams', 'Jon at han Adams'),
('Jonathan Adams', 'Jonathan Adams'),
('Jonathan Adams', 'Jonathan Adens')
)
@ddt.unpack
def test_name_update_requires_idv_invalid_edits(self, old_name, new_name):
"""
Test that a name change is blocked through this API if it requires ID verification.
In this case, the user has invalid name edits
"""
validator = NameChangeValidator([], 1, old_name, new_name)
self.assertFalse(validator.validate())

def test_name_update_requires_idv_name_changes(self):
"""
Test that a name change is blocked through this API if it requires ID verification.
In this case, the user has previously changed their name 2 or more times
"""
validator = NameChangeValidator(['Old Name 1'], 1, 'Old Name 2', 'New Name')
self.assertFalse(validator.validate())

@ddt.data(
('Jonathan Adams', 'Jonathan Q Adams'),
('Jonathan Adams', 'Jonathan Adam'),
('Jonathan Adams', 'Jo nathan Adams'),
('Jonathan Adams', 'Jonatha N Adams'),
('Jonathan Adams', 'Jonathan Adáms'),
('Jonathan Adáms', 'Jonathan Adæms'),
('Jonathan Adams', 'Jonathan A\'dams'),
('李陈', '李王')
)
@ddt.unpack
def test_name_update_does_not_require_idv_valid_edits(self, old_name, new_name):
"""
Test that the user can change their name freely if it does not require verification.
"""
validator = NameChangeValidator([], 1, old_name, new_name)
self.assertTrue(validator.validate())

def test_name_update_does_not_require_idv_no_certificate(self):
"""
Test that the user can change their name freely if they have no certificates
"""
validator = NameChangeValidator(['Really Old Name', 'Very Old Name'], 0, 'Old Name', 'New Name')
self.assertTrue(validator.validate())

0 comments on commit 72f4224

Please sign in to comment.