-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from edx/alangsto/validate_name_change
feat: Add name change validator
- Loading branch information
Showing
4 changed files
with
167 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |