From d25d29cf0c8c194d1d8f8416b87b77e282a94662 Mon Sep 17 00:00:00 2001 From: Travis Dart Date: Mon, 6 Jul 2020 23:33:46 -0500 Subject: [PATCH] Uniqueness tests implemented. --- formstorm/FormElement.py | 15 ++-------- formstorm/FormTest.py | 60 ++++++++++++++++++++++++++++----------- tests/fstestapp/models.py | 1 - tests/fstestapp/test.py | 2 +- 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/formstorm/FormElement.py b/formstorm/FormElement.py index 0c79ee9..d303ec8 100644 --- a/formstorm/FormElement.py +++ b/formstorm/FormElement.py @@ -4,20 +4,10 @@ class FormElement(object): - def __init__(self, - good=[], - bad=[], - values=[], - only_if=[], - not_if=[], - unique=False, - fk_field=None): + def __init__(self, good=[], bad=[], is_unique=False): self.bad = bad self.good = good - self.values = values - self.only_if = only_if - self.not_if = not_if - + self.is_unique = is_unique def build_iterator(self, form, field_name, is_e2e): """ @@ -67,6 +57,5 @@ def _replace_all_q(value_list): self.iterator = chain( [(x, True) for x in self.good], [(x, False) for x in self.bad], - self.values ) return self.iterator diff --git a/formstorm/FormTest.py b/formstorm/FormTest.py index 0bf4dd9..59dbd69 100644 --- a/formstorm/FormTest.py +++ b/formstorm/FormTest.py @@ -18,19 +18,24 @@ def submit_form(self, form_values): def _build_elements(self, fields_to_ignore=[]): elements = {} + unique_elements = [] for e in dir(self): if e in fields_to_ignore: continue # Filter out this class's FormElement properties - if type(getattr(self, e)) is FormElement: - elements[e] = getattr(self, e).build_iterator( + form_element = getattr(self, e) + if type(form_element) is FormElement: + elements[e] = form_element.build_iterator( is_e2e=self.is_e2e, form=self.form, field_name=e ) - return elements + if form_element.is_unique: + unique_elements.append(e) + + return elements, unique_elements def __init__(self): self._is_modelform = ModelForm in self.form.mro() @@ -50,7 +55,7 @@ def __init__(self): # ('AA...A', False) # ] # } - elements = self._build_elements() + elements, self.unique_elements = self._build_elements() # Build iterable from the iterables of the sub-objects. For example: # self._iterator = [ @@ -72,7 +77,6 @@ def __init__(self): # {'subtitle': ('AA...A', False), 'title': ('AA...A', False) } # ] self._iterator = dict_combo(elements) - # Generate test values for multi-field validation: for v in getattr(self, "additional_values", {}): # The easiest way to explain the v2 operation is by example: @@ -80,12 +84,25 @@ def __init__(self): # v2 = {"one": (1, True), "two": (2, True)} v2 = dict(zip(v[0].keys(), [(y, v[1]) for y in v[0].values()])) # Build all the combinations of the other fields. - new_elements = self._build_elements(fields_to_ignore=v[0].keys()) + new_elements = self._build_elements( + fields_to_ignore=v[0].keys() + )[0] # Just take the elements and discard the unique_elements. # Combine the values with the element from additional_values. addl_iterator = dict_combo(new_elements, base_dict=v2) self._iterator = itertools.chain(self._iterator, addl_iterator) - def _run(self, is_uniqueness_test=False): + def run(self): + # To run uniqueness tests, the form must be a modelform, + # and at least some good values must be specified. + # Don't run if there aren't any unique_elements. + should_run_uniqueness_test = True if self.unique_elements else False + has_run_uniqueness_test = False if self.unique_elements else True + + if should_run_uniqueness_test and not self._is_modelform: + raise RuntimeError( + "Uniqueness tests can only be run on ModelForms" + ) + # i is a dictionary containing tuples in the form (value, is_good) for i in self._iterator: # if any field is invalid, the form is invalid. @@ -94,7 +111,7 @@ def _run(self, is_uniqueness_test=False): # Remove None values from dict. form_values = {k: v[0] for k, v in i.items() if v[0] is not None} - if self._is_modelform and not is_uniqueness_test: + if self._is_modelform: sid = transaction.savepoint() self.submit_form(form_values) @@ -104,16 +121,27 @@ def _run(self, is_uniqueness_test=False): # print("errors", self.bound_form.errors) raise AssertionError - if is_uniqueness_test and form_is_good: - self.submit_form(form_values) - assert not self.is_good() + if self._is_modelform: + if not has_run_uniqueness_test and form_is_good: + # There's no way to verify that the uniqueness constraint + # was the one that triggered the error. However, if the + # form was previously valid, and now it's not, and we've + # submitted the same input, then we can conclude that + # non-uniqueness was the issue. + self.submit_form(form_values) + assert not self.is_good() + for field in self.unique_elements: + assert self.bound_form.has_error(field) + has_run_uniqueness_test = True - if self._is_modelform and not is_uniqueness_test: transaction.savepoint_rollback(sid) - - def run(self): - self._run(is_uniqueness_test=False) - # self._run(is_uniqueness_test=True) + + if should_run_uniqueness_test and not has_run_uniqueness_test: + # If we make it to the end without having run the uniqueness test, + # then it must be because no good input was specified. + raise RuntimeError( + "Good input must be given to run uniqueness test." + ) def run_uniqueness_tests(self): pass diff --git a/tests/fstestapp/models.py b/tests/fstestapp/models.py index a071ed9..2fa013c 100644 --- a/tests/fstestapp/models.py +++ b/tests/fstestapp/models.py @@ -12,7 +12,6 @@ class Genre(models.Model): class Book(models.Model): def clean(self): - # Don't allow draft entries to have a pub_date. if len(self.title) + len(self.subtitle) > 150: raise ValidationError( "Title and subtitle can't have a combined length greater than " diff --git a/tests/fstestapp/test.py b/tests/fstestapp/test.py index f7617e6..32fef9c 100644 --- a/tests/fstestapp/test.py +++ b/tests/fstestapp/test.py @@ -11,6 +11,7 @@ class BookFormTest(FormTest): title = FormElement( good=["Moby Dick"], bad=[None, "", "A"*101], + is_unique=True ) subtitle = FormElement( good=[None, "", "or The Whale"], @@ -110,7 +111,6 @@ def test_dict_combo_with_base_dict(self): assert list(x) == y - class BookTestCase(TestCase): def setUp(self): Genre(name="Mystery").save()