-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Standalone validation (T30324) #167
Open
jeroenvanriel
wants to merge
12
commits into
CodeYellowBV:master
Choose a base branch
from
jeroenvanriel:validate-only
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 11 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
6a96bef
Fix horrible typo
fd3c049
Add standalone validation feature
ea061e6
Correctly extract querystring parameter for PUT
796d323
Raise the correct exception when flag is not set
dccd770
Improve comment
4280841
Fix stupid logic
0d33abd
Merge branch 'master' of https://github.com/CodeYellowBV/django-binde…
b83003c
Stop earlier when flag not set
31a61bf
Add tests for validation flow.
9be35a2
Some cleanup
ba74f3c
Merge remote-tracking branch 'origin' into validate-only
d363670
Add a comment in the exception
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,228 @@ | ||
from re import I | ||
from tests.testapp.models import contact_person | ||
from tests.testapp.models.contact_person import ContactPerson | ||
from django.test import TestCase, Client | ||
|
||
import json | ||
from binder.json import jsonloads | ||
from django.contrib.auth.models import User | ||
from .testapp.models import Animal, Caretaker, ContactPerson | ||
|
||
|
||
class TestModelValidation(TestCase): | ||
""" | ||
Test the validate-only functionality. | ||
|
||
We check that the validation is executed as normal, but that the models | ||
are not created when the validate query paramter is set to true. | ||
|
||
We check validation for: | ||
- post | ||
- put | ||
- multi-put | ||
- delete | ||
""" | ||
|
||
|
||
def setUp(self): | ||
super().setUp() | ||
u = User(username='testuser', is_active=True, is_superuser=True) | ||
u.set_password('test') | ||
u.save() | ||
self.client = Client() | ||
r = self.client.login(username='testuser', password='test') | ||
self.assertTrue(r) | ||
|
||
# some update payload | ||
self.model_data_with_error = { | ||
'name': 'very_special_forbidden_contact_person_name', # see `contact_person.py` | ||
} | ||
self.model_data = { | ||
'name': 'Scooooooby', | ||
} | ||
|
||
|
||
### helpers ### | ||
|
||
|
||
def assert_validation_error(self, response, person_id=None): | ||
if person_id is None: | ||
person_id = 'null' # for post | ||
|
||
self.assertEqual(response.status_code, 400) | ||
|
||
returned_data = jsonloads(response.content) | ||
|
||
# check that there were validation errors | ||
self.assertEqual(returned_data.get('code'), 'ValidationError') | ||
|
||
# check that the validation error is present | ||
validation_error = returned_data.get('errors').get('contact_person').get(str(person_id)).get('__all__')[0] | ||
self.assertEqual(validation_error.get('code'), 'invalid') | ||
self.assertEqual(validation_error.get('message'), 'Very special validation check that we need in `tests.M2MStoreErrorsTest`.') | ||
|
||
|
||
def assert_multi_put_validation_error(self, response): | ||
self.assertEqual(response.status_code, 400) | ||
|
||
returned_data = jsonloads(response.content) | ||
|
||
# check that there were validation errors | ||
self.assertEqual(returned_data.get('code'), 'ValidationError') | ||
|
||
# check that all (two) the validation errors are present | ||
for error in returned_data.get('errors').get('contact_person').values(): | ||
validation_error = error.get('__all__')[0] | ||
self.assertEqual(validation_error.get('code'), 'invalid') | ||
self.assertEqual(validation_error.get('message'), 'Very special validation check that we need in `tests.M2MStoreErrorsTest`.') | ||
|
||
|
||
### tests ### | ||
|
||
|
||
def assert_no_validation_error(self, response): | ||
self.assertEqual(response.status_code, 200) | ||
|
||
# check that the validation was successful | ||
returned_data = jsonloads(response.content) | ||
self.assertEqual(returned_data.get('code'), 'SkipSave') | ||
self.assertEqual(returned_data.get('message'), 'No validation errors were encountered.') | ||
|
||
|
||
def test_validate_on_post(self): | ||
self.assertEqual(0, ContactPerson.objects.count()) | ||
|
||
# trigger a validation error | ||
response = self.client.post('/contact_person/?validate=true', data=json.dumps(self.model_data_with_error), content_type='application/json') | ||
self.assert_validation_error(response) | ||
self.assertEqual(0, ContactPerson.objects.count()) | ||
|
||
# now without validation errors | ||
response = self.client.post('/contact_person/?validate=true', data=json.dumps(self.model_data), content_type='application/json') | ||
self.assert_no_validation_error(response) | ||
self.assertEqual(0, ContactPerson.objects.count()) | ||
|
||
# now for real | ||
response = self.client.post('/contact_person/', data=json.dumps(self.model_data), content_type='application/json') | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual('Scooooooby', ContactPerson.objects.first().name) | ||
|
||
|
||
def test_validate_on_put(self): | ||
person_id = ContactPerson.objects.create(name='Scooby Doo').id | ||
self.assertEqual('Scooby Doo', ContactPerson.objects.first().name) | ||
|
||
# trigger a validation error | ||
response = self.client.put(f'/contact_person/{person_id}/?validate=true', data=json.dumps(self.model_data_with_error), content_type='application/json') | ||
self.assert_validation_error(response, person_id) | ||
self.assertEqual('Scooby Doo', ContactPerson.objects.first().name) | ||
|
||
# now without validation errors | ||
response = self.client.put(f'/contact_person/{person_id}/?validate=true', data=json.dumps(self.model_data), content_type='application/json') | ||
self.assert_no_validation_error(response) | ||
self.assertEqual('Scooby Doo', ContactPerson.objects.first().name) | ||
|
||
# now for real | ||
response = self.client.put(f'/contact_person/{person_id}/', data=json.dumps(self.model_data), content_type='application/json') | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual('Scooooooby', ContactPerson.objects.first().name) | ||
|
||
|
||
def test_validate_on_multiput(self): | ||
person_1_id = ContactPerson.objects.create(name='Scooby Doo 1').id | ||
person_2_id = ContactPerson.objects.create(name='Scooby Doo 2').id | ||
|
||
multi_put_data = {'data': [ | ||
{ | ||
'id': person_1_id, | ||
'name': 'New Scooby', | ||
}, | ||
{ | ||
'id': person_2_id, | ||
'name': 'New Doo' | ||
} | ||
]} | ||
|
||
multi_put_data_with_error = {'data': [ | ||
{ | ||
'id': person_1_id, | ||
'name': 'very_special_forbidden_contact_person_name', | ||
}, | ||
{ | ||
'id': person_2_id, | ||
'name': 'very_special_forbidden_contact_person_name' | ||
} | ||
]} | ||
|
||
# trigger a validation error | ||
response = self.client.put(f'/contact_person/?validate=true', data=json.dumps(multi_put_data_with_error), content_type='application/json') | ||
self.assert_multi_put_validation_error(response) | ||
self.assertEqual('Scooby Doo 1', ContactPerson.objects.get(id=person_1_id).name) | ||
self.assertEqual('Scooby Doo 2', ContactPerson.objects.get(id=person_2_id).name) | ||
|
||
|
||
# now without validation error | ||
response = self.client.put(f'/contact_person/?validate=true', data=json.dumps(multi_put_data), content_type='application/json') | ||
self.assert_no_validation_error(response) | ||
self.assertEqual('Scooby Doo 1', ContactPerson.objects.get(id=person_1_id).name) | ||
self.assertEqual('Scooby Doo 2', ContactPerson.objects.get(id=person_2_id).name) | ||
|
||
# now for real | ||
response = self.client.put(f'/contact_person/', data=json.dumps(multi_put_data), content_type='application/json') | ||
self.assertEqual(response.status_code, 200) | ||
self.assertEqual('New Scooby', ContactPerson.objects.get(id=person_1_id).name) | ||
self.assertEqual('New Doo', ContactPerson.objects.get(id=person_2_id).name) | ||
|
||
|
||
def test_validate_on_delete(self): | ||
'''Check if deletion is cancelled when we only attempt to validate | ||
the delete operation. This test only covers validation of the | ||
on_delete=PROTECT constraint of a fk.''' | ||
|
||
def is_deleted(obj): | ||
'''Whether the obj was soft-deleted, so when the 'deleted' | ||
attribute is not present, or when it is True.''' | ||
|
||
try: | ||
obj.refresh_from_db() | ||
except obj.DoesNotExist: | ||
return True # hard-deleted | ||
return animal.__dict__.get('deleted') or False | ||
|
||
|
||
# animal has a fk to caretaker with on_delete=PROTECT | ||
caretaker = Caretaker.objects.create(name='Connie Care') | ||
animal = Animal.objects.create(name='Pony', caretaker=caretaker) | ||
|
||
|
||
### with validation error | ||
|
||
response = self.client.delete(f'/caretaker/{caretaker.id}/?validate=true') | ||
# assert validation error | ||
# and check that it was about the PROTECTED constraint | ||
self.assertEqual(response.status_code, 400) | ||
returned_data = jsonloads(response.content) | ||
self.assertEqual(returned_data.get('code'), 'ValidationError') | ||
self.assertEqual(returned_data.get('errors').get('caretaker').get(str(caretaker.id)).get('id')[0].get('code'), 'protected') | ||
|
||
self.assertFalse(is_deleted(caretaker)) | ||
|
||
|
||
### without validation error | ||
|
||
# now we delete the animal to make sure that deletion is possible | ||
# note that soft-deleting will of course not remove the validation error | ||
animal.delete() | ||
|
||
# now no validation error should be trown | ||
response = self.client.delete(f'/caretaker/{caretaker.id}/?validate=true') | ||
print(response.content) | ||
self.assert_no_validation_error(response) | ||
|
||
self.assertFalse(is_deleted(caretaker)) | ||
|
||
|
||
### now for real | ||
|
||
response = self.client.delete(f'/caretaker/{caretaker.id}/') | ||
self.assertTrue(is_deleted(caretaker)) |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add in the error a small description on how to enable this standalone_validation? e.g. add allow_standalone_validation = True on the model Foo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my latest commit