diff --git a/api/test/testcases.py b/api/test/testcases.py new file mode 100644 index 0000000..fb879e4 --- /dev/null +++ b/api/test/testcases.py @@ -0,0 +1,694 @@ +from unittest import mock + +from django.contrib.auth import get_user_model +from django.utils import timezone +from django.urls import reverse + +from rest_framework import status + +User = get_user_model() + + +class GenericAPITestCaseMixin: + """ + Base mixin for testing one or more operations on a model. + + The specifics of each operation is not implemented here. + Each operation has its own TestCase, which relies on methods/attributes + defined here (some or all). + + The "real" mixins for each operation are: + [List,Create,Retrieve,Update,Destroy]APITestCaseMixin. + + Example: + + E.g. for a creation test, your testcase should be something like: + + class MyModelCreateAPITestCase( + CreateAPITestCaseMixin, + GenericAPITestCaseMixin, + APITestCase, + ): + ... + + Attributes: + # General + model (models.Model): The model class under test. + url_label (str): Use to get the API urls of the model under test: + - The model url is:`reverse('{url_label}-list). + This is used by `[List,Create]APITestCaseMixin`. + The url can also be customized by defining `get_url_model` + method. + - The url of a model object is: `reverse('{label}-detail')`. + This is used by `[Retrieve,Update,Destroy]APITestCaseMixin`. + The url can also be customized by defining `get_url_object` + method. + + # Authentication + user_auth (User instance): If given, this user is authenticated for + each request sent to the API. Default to `None`. + See section 'Authenticate requests'. + user_auth_mapping (dict of str: User instance): Associates a user to + authenticate for each type of API request. + See section 'Authenticate requests'. + + Authenticate requests: + Each real testcase call `get_user` method with a label for the + operation under test (e.g. UpdateAPITestCaseMixin will call + `get_user('update')`. If it returns a user, this latter is + authenticated with `force_authenticate` method of the test client. + + The default behavior, which can be customized by replacing `get_user` + method, is to return `user_auth` attribute/property if not None. Else, + `user_auth_mapping[operation_type_label]` or None if key is missing. + + A basic user is created during setUp. It can be found as `user` + attribute. + + """ + model = None + url_label = '' + + user_auth = None + user_auth_mapping = {} + + def setUp(self): + """ + Prepare a test execution. + + Don't forget to call super().setUp() if you define this in subclass. + """ + # Dict comparison can be large. + self.maxDiff = 2000 + + # This will be returned by django.utils.timezone.now thanks to the mock + # in test methods. + # This can be useful to check auto_now and auto_now_add fields. + self.now = timezone.now() + + # A very basic user. + self.user = User.objects.create_user( + username='user', password='user', + first_name='first', last_name='last', + ) + + def get_user(self, action): + """ + Returns a user to authenticate for requests of type 'action'. + + Property `user_auth` has precedence over the property + `user_auth_mapping`. + + Args: + action (str): Operation label. See LCRUD TestCases for reserved + labels, before setting your own if you create a new common + operation TestCase. + + Returns: + User instance | None: + If None, no user should be authenticated for this request. + Else, the returned user should be authenticated. + """ + if self.user_auth is not None: + return self.user_auth + return self.user_auth_mapping.get(action) + + def get_url_model(self, *args, **kwargs): + """ + Returns the API url for the model. + + Default to a reverse on `{url_label}-list`. + + Used by `[List,Create]APITestCaseMixin`. + + """ + return reverse( + '{label}-list'.format(label=self.url_label), + args=args, kwargs=kwargs, + ) + + def get_url_object(self, *args, **kwargs): + """ + Returns the API url for a particular object of model. + + Default to a reverse on `{url_label}-detail`. + + Used by `[Retrieve,Update,Destroy]APITestCaseMixin`. + + """ + return reverse( + '{label}-detail'.format(label=self.url_label), + args=args, kwargs=kwargs, + ) + + def db_create(self, data): + """ + Create a model instance in DB from data given as argument. + + `data` can define M2M fields. + + Operations: + - Create object from non-M2M fields. + - Link the M2M fields with `set` on the created object. + + Returns: + The created object. + """ + fields, m2m_fields = {}, {} + for name, value in data.items(): + if self.model._meta.get_field(name).many_to_many: + m2m_fields[name] = value + else: + fields[name] = value + + instance = self.model.objects.create(**fields) + for name, value in m2m_fields.items(): + getattr(instance, name).set(value) + + return instance + + def get_expected_data(self, instance): + """ + Returns the data the API should reply for the object `instance`. + + This should be the same result of the serializer used for the sent + request. + + Must be defined by subclasses, except for DestroyAPITestCaseMixin. + + Args: + instance (a `model` object) + + Returns: + JSON-decoded data of this instance. + + """ + raise NotImplementedError( + "Subclass must implement 'get_expected_data(instance)' method." + ) + + def assertInstanceEqual(self, instance, expected): + """ + Checks if instance verifies expected. + + For each key/value pair of expected, it verifies that instance.key is + equal to value. `assertEqual` is used, except for M2M fields where + `assertQuerysetEqual(ordered=True, ...)` is used. + + Args: + instance (`model` object): E.g. obtained from DB after a create + operation. + expected (dict): Expected data of instance. + + """ + for key, exp_value in expected.items(): + field = self.model._meta.get_field(key) + value = getattr(instance, key) + if field.many_to_many: + self.assertQuerysetEqual( + value.all(), map(repr, exp_value), + ordered=False, + ) + else: + self.assertEqual(value, exp_value) + + @property + def instances_data(self): + """ + Instances data of the model which will be created if necessary. + + For example, ListAPITestCaseMixin uses these before sending the list + request. + + The first is also used, if `instance_data` is not re-defined, for RUD + operations. + + """ + raise NotImplementedError( + "Property or attribute 'instances_data' must be declared by " + "subclass." + ) + + @property + def instance_data(self): + """ + Data of a single instance of model. + + For example, this data is used to create the instance of model to apply + RUD operations. + + Default to the first item of the `instances_data` property. + + """ + return self.instances_data[0] + + +class ListAPITestCaseMixin: + """ + Mixin to test the "List" API request of a model. + + Has hooks to check the returned objects and their ordering. + + Authentication: + This operation use the label 'list'. + See `GenericAPITestCaseMixin`#`Authenticate requests` for further + informations. + + Attributes: + list_ordering (list of str | str): The ordering of objects that should + be respected by the API response. + Same format as the ordering Meta attribute of models, e.g. + '-created_at' or ['-event', 'created_at']. + Ordering on '__' fields are currently not supported. + + Todo: + * Allow '__' in ordering. + * Support pages. Currently we expect returned objects are on a single + page. + + """ + list_ordering = [] + + @property + def list_expected(self): + """ + Model instances the API should returned. + + Default to all objects of models. + Eventually sorted according to `list_ordering` attribute. + + """ + qs = self.model.objects.all() + if self.list_ordering: + qs = qs.order_by(self.list_ordering) + return qs + + @property + def list_expected_count(self): + """ + Number of objects the API should returned. + + Default to the length of expected returned objects. + """ + return len(self.list_expected) + + @property + def list_expected_ordering(self): + """ + Use this to get expected ordering, instead of relying directly on the + `list_ordering`, if you write subclasses. + """ + if isinstance(self.list_ordering, (tuple, list)): + return self.list_ordering + return [self.list_ordering] + + def assertOrdered(self, results, ordering): + """ + Check `results` are sorted according to `ordering`. + + Attributes: + results (list of model instances) + ordering (list of fields as str) + + """ + _ordering = [ + (f[1:], True) if f.startswith('-') else (f, False) + for f in ordering + ] + + it = iter(results) + + previous = next(it) + + for current in it: + self._assertOrdered(previous, current, _ordering) + previous = current + + def _assertOrdered(self, d1, d2, ordering): + if not ordering: + return True + + field, desc = ordering[0] + + v1, v2 = d1[field], d2[field] + + if v1 == v2: + self._assertOrdered(v1, v2, ordering[1:]) + elif desc: + self.assertGreater(v1, v2, msg=( + "Ordering on '%s' (DESC) is not respected for the following " + "objects.\n- First: %r.\n- Second: %r." + % (field, d1, d2) + )) + else: + self.assertLess(v1, v2, msg=( + "Ordering on '%s' (ASC) is not respected for the following " + "objects:\n- First: %r.\n- Second: %r." + % (field, d1, d2) + )) + + @mock.patch('django.utils.timezone.now') + def test_list(self, mock_now): + """ + The test. + + Preparation: + - Create instances from `instances_data` property. + - Optionally, authenticate client. + + Execution: + - HTTP GET request on model url. + + Check: + - Response status code: 200. + - The base response: count (pages not supported). + - Results (serialized instances) are formatted as expected and + their ordering. + + """ + mock_now.return_value = self.now + + # Setup database. + + for data in self.instances_data: + self.db_create(data) + + # Call API to get instances list. + user = self.get_user('list') + if user: + self.client.force_authenticate(user) + + r = self.client.get(self.get_url_model()) + + # Check API response. + self.assertEqual(r.status_code, status.HTTP_200_OK) + + base_response = { + 'count': self.list_expected_count, + 'previous': None, + 'next': None, + } + self.assertDictContainsSubset(base_response, r.data) + + results = r.data['results'] + + # Check API response ordering. + self.assertOrdered(results, self.list_expected_ordering) + + # Check API response data. + for result, instance in zip(results, self.list_expected): + self.assertDictEqual(result, self.get_expected_data(instance)) + + +class CreateAPITestCaseMixin: + """ + Mixin to test the "Create" API request of a model. + + Authentication: + This operation use the label 'create'. + See `GenericAPITestCaseMixin`#`Authenticate requests` for further + informations. + + Note: + The request is sent assuming the payload is JSON. + + """ + + @property + def create_data(self): + """ + Payload of the create request sent to the API. + """ + raise NotImplementedError( + "Subclass must define a 'create_data' attribute/property. This is " + "the payload of the POST request to the model url." + ) + + @property + def create_expected(self): + """ + Data of model instance which should be created. + """ + raise NotImplementedError( + "Subclass must define a create_expected attribute/property. It is " + "a dict-like object whose value should be equal to the key " + "attribute of the created instance." + ) + + @mock.patch('django.utils.timezone.now') + def test_create(self, mock_now): + """ + The test. + + Preparation: + - Optionally, authenticate client. + + Execution: + - HTTP POST request on model url. Payload from `create_data`. + + Check: + - Response status code: 201. + - Instance has been created in DB. + - Instance created is as expected (check with `create_expected`). + - Instance is correctly serialized (in response' data). + + """ + mock_now.return_value = self.now + + # Call API to create an instance of model. + user = self.get_user('create') + if user: + self.client.force_authenticate(user) + + r = self.client.post(self.get_url_model(), self.create_data) + + # Check database. + instances = self.model.objects.all() + self.assertEqual(len(instances), 1) + + instance = instances[0] + self.assertInstanceEqual(instance, self.create_expected) + + # Check API response. + self.assertEqual(r.status_code, status.HTTP_201_CREATED) + self.assertDictEqual(r.data, self.get_expected_data(instance)) + + +class RetrieveAPITestCaseMixin: + """ + Mixin to test the "Retrieve" API request of a model object. + + Authentication: + This operation use the label 'retrieve'. + See `GenericAPITestCaseMixin`#`Authenticate requests` for further + informations. + + """ + + @mock.patch('django.utils.timezone.now') + def test_retrieve(self, mock_now): + """ + The test. + + Preparation: + - Create a model instance from `instance_data` property. + - Optionally, authenticate client. + + Execution: + - Get url of the object with its pk. + - HTTP GET request on this url. + + Check: + - Response status code: 200. + - Instance is correctly serialized (in response' data). + + """ + mock_now.return_value = self.now + + # Setup database. + data = self.instance_data + instance = self.db_create(data) + + # Call API to retrieve the event data. + user = self.get_user('retrieve') + if user: + self.client.force_authenticate(user) + + r = self.client.get(self.get_url_object(pk=1)) + + # Check API response. + self.assertEqual(r.status_code, status.HTTP_200_OK) + self.assertDictEqual(r.data, self.get_expected_data(instance)) + + +class UpdateAPITestCaseMixin: + """ + Mixin to test the "Update" API request of a model. + + Authentication: + This operation use the label 'update'. + See `GenericAPITestCaseMixin`#`Authenticate requests` for further + informations. + + Notes: + * A single test using partial update / PATCH HTTP method is written. + * The request is sent assuming the payload is JSON. + + Todo: + * Add test for update / PUT HTTP method. + + """ + + @property + def update_data(self): + """ + Payload of the update request sent to the API. + """ + raise NotImplementedError( + "Subclass must define a update_data attribute/property. This is " + "the payload of the PUT request to the instance url." + ) + + @property + def update_expected(self): + """ + Data of model instance which should be updated. + """ + raise NotImplementedError( + "Subclass must define a update_expected attribute/property. It is " + "a dict-like object whose value should be equal to the key " + "attribute of the updated instance." + ) + + @mock.patch('django.utils.timezone.now') + def test_update(self, mock_now): + """ + The test. + + Preparation: + - Create a model instance from `instance_data` property. + - Optionally, authenticate client. + + Execution: + - Get url of the object with its pk. + - HTTP PATCH request on this url. Payload from `update_data`. + + Check: + - Response status code: 200. + - No instance has been created or deleted in DB. + - Updated instance is as expected (check with `update_expected`). + - Instance is correctly serialized (in response' data). + + """ + mock_now.return_value = self.now + + # Setup database. + data = self.instance_data + instance = self.db_create(data) + + # Call API to update the event. + user = self.get_user('update') + if user: + self.client.force_authenticate(user) + + r = self.client.patch(self.get_url_object(pk=1), self.update_data) + + # Check database. + instances = self.model.objects.all() + self.assertEqual(len(instances), 1) + + instance.refresh_from_db() + self.assertInstanceEqual(instance, self.update_expected) + + # Check API response. + self.assertEqual(r.status_code, status.HTTP_200_OK) + self.assertDictEqual(r.data, self.get_expected_data(instance)) + + +class DestroyAPITestCaseMixin: + """ + Mixin to test the "Destroy" API request of a model. + + Authentication: + This operation use the label 'destroy'. + See `GenericAPITestCaseMixin`#`Authenticate requests` for further + informations. + + """ + + @mock.patch('django.utils.timezone.now') + def test_destroy(self, mock_now): + """ + The test. + + Preparation: + - Create a model instance from `instance_data` property. + - Optionally, authenticate client. + + Execution: + - Get url of the object with its pk. + - HTTP DELETE request on this url. + + Check: + - Response status code: 204. + - Instance is no longer present in DB. + + """ + mock_now.return_value = self.now + + # Setup database. + data = self.instance_data + instance = self.db_create(data) + + # Call API to update the event. + user = self.get_user('destroy') + if user: + self.client.force_authenticate(user) + + r = self.client.delete(self.get_url_object(pk=1)) + + # Check database. + instances = self.model.objects.all() + self.assertEqual(len(instances), 0) + + with self.assertRaises(self.model.DoesNotExist): + instance.refresh_from_db() + + # Check API response. + self.assertEqual(r.status_code, status.HTTP_204_NO_CONTENT) + self.assertIsNone(r.data) + + +class ModelAPITestCaseMixin( + ListAPITestCaseMixin, CreateAPITestCaseMixin, RetrieveAPITestCaseMixin, + UpdateAPITestCaseMixin, DestroyAPITestCaseMixin, + GenericAPITestCaseMixin, + ): + """ + Tests all LCRUD operations on a model. + + See general docs in GenericAPITestCaseMixin. + See docs for a specific operation in [Operation]APITestCaseMixin. + + Example: + + class EventAPITestCase(ModelAPITestCaseMixin, APITestCase): + model = Event + ... + + @property + def create_data(self): + ... + + @property + def create_expected(self): + ... + + ... + + If you want to run only a few operations, consider using + specific-mixins individually. You can still have something as + `EventAPITestCaseMixin` to provide common atributes/properties/methods. + + """ + pass diff --git a/api/test/utils.py b/api/test/utils.py new file mode 100644 index 0000000..d6778a3 --- /dev/null +++ b/api/test/utils.py @@ -0,0 +1,19 @@ +import datetime + + +def _json_format(value): + if isinstance(value, datetime.datetime): + return value.isoformat().replace('+00:00', 'Z') + return value + + +def json_format(to_format): + """ + Returns value formatted like json output of the API. + + Supported type of value: + * datetime + """ + if type(to_format) == dict: + return {key: _json_format(value) for key, value in to_format.items()} + return _json_format(to_format) diff --git a/api/testcases.py b/api/testcases.py deleted file mode 100644 index d91f3cb..0000000 --- a/api/testcases.py +++ /dev/null @@ -1,292 +0,0 @@ -from datetime import timedelta -from django.contrib.auth import get_user_model -from django.db.models import Q -from django.utils import timezone -from django.urls import reverse - -from rest_framework.test import APIRequestFactory -from rest_framework import status - -from event.models import Event, Place, ActivityTag, ActivityTemplate - - -User = get_user_model() - - -class DataBaseMixin(object): - """ - provides a datatabse for API tests - """ - @classmethod - def setUpTestData(cls): - # Users - cls.user1_data = {'username': "user1", 'password': "pass1"} - cls.user1 = User.objects.create_user(**cls.user1_data) - - def setUp(self): - # Events - self.event1_data = {'title': "event1", 'slug': "slug1", - 'beginning_date': timezone.now() - + timedelta(days=30), - "description": "C'est trop cool !", - 'ending_date': timezone.now()+timedelta(days=31), - 'created_by': self.user1, } - self.event2_data = {"title": "test event", "slug": "test-event", - "description": "C'est trop cool !", - "beginning_date": "2017-07-18T18:05:00Z", - "ending_date": "2017-07-19T18:05:00Z", } - self.event1 = Event.objects.create(**self.event1_data) - - # ActivityTags - self.tag1_data = {"name": "tag1", "is_public": False, "color": "#111"} - self.tag2_data = {"name": "tag2", "is_public": False, "color": "#222"} - self.tag3_data = {"name": "tag3", "is_public": False, "color": "#333"} - self.tag1 = ActivityTag.objects.create(**self.tag1_data) - - self.act_temp1_data = {'title': "act temp1", 'is_public': True, - 'remarks': "test remark", 'event': self.event1} - self.act_temp2_data = {'title': "act temp2", 'is_public': False, - 'remarks': "test remark", - 'tags': [self.tag2_data, ]} - self.act_temp1 = ActivityTemplate.objects.create(**self.act_temp1_data) - self.act_temp1.tags.add(self.tag1) - - -class EventBasedModelTestMixin(DataBaseMixin): - """ - Note : need to define : - `model`: the model served by the API - `base_name`: the base_name used in the URL - `initial_count`: (will disappear) inital count in the db - `data_creation`: name in db used to create new instance - `instance_name`: existing instance name in the db - used for update/delete - `field_tested`: name of field tested in the update test - `serializer`: serialiser used for the API - - tests for models served by the API that are related to an event - and whose API urls are nested under ../event//%model - """ - def user_create_extra(self): - """ - extra test in creation by a permited user - """ - pass - - def pre_update_extra(self, data): - """ - extra modification for the data sent for update - """ - return data - - def post_update_extra(self): - """ - extra test for updated model - """ - pass - - def test_user_create(self): - """ - ensure a permited user can create a new %model object using API - """ - data = getattr(self, self.data_creation) - - event_id = self.event1.id - url = reverse('{base_name}-list'.format(base_name=self.base_name), - kwargs={'event_pk': event_id}) - self.client.force_authenticate(user=self.user1) - response = self.client.post(url, data, - format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(self.model.objects.count(), self.initial_count + 1) - self.assertEqual(self.model.objects.get(id=self.initial_count+1).event, - self.event1) - - self.user_create_extra() - - def test_user_update(self): - """ - ensure a permited user can update a new %model object using API - """ - instance = getattr(self, self.instance_name) - factory = APIRequestFactory() - - instance_id = instance.id - event_id = self.event1.id - url = reverse('{base_name}-list'.format(base_name=self.base_name), - kwargs={'event_pk': event_id}) - url = "%s%d/" % (url, instance_id) - - request = factory.get(url) - data = self.serializer(instance, context={'request': request}).data - - newvalue = "I'm a test" - data[self.field_tested] = newvalue - - data = self.pre_update_extra(data) - - self.client.force_authenticate(user=self.user1) - response = self.client.patch(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - instance.refresh_from_db() - self.assertEqual(getattr(instance, self.field_tested), newvalue) - - self.post_update_extra(instance) - - def test_user_delete(self): - """ - ensure a permited user can delete a new %model object using API - """ - instance = getattr(self, self.instance_name) - - instance_id = instance.id - event_id = self.event1.id - url = reverse('{base_name}-list'.format(base_name=self.base_name), - kwargs={'event_pk': event_id}) - url = "%s%d/" % (url, instance_id) - - self.client.force_authenticate(user=self.user1) - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(self.model.objects.count(), self.initial_count - 1) - - -# TODO rajouter la gestion des permissions dans le Mixin -# TODO rajouter un test pour s'assurer que les personnes non -# connectées ne peuvent pas create/update/delete -class EventSpecificTestMixin(object): - """ - Tests is the EventSpecifics querysets are rendered correctly - using the API - Note : need to define : - `model`: the concerned model serve by the API - `root_base_name`: the base_name used in the root-level urls - `event_base_name`: the base_name used in the event-level urls - - tests for models served by the API that inherit EventSpecificMixin - """ - @classmethod - def setUpTestData(cls): - # Users - cls.user1_data = {'username': "user1", 'password': "pass1"} - cls.user1 = User.objects.create_user(**cls.user1_data) - # Events - cls.event1_data = {'title': "event1", 'slug': "slug1", - 'beginning_date': timezone.now() - + timedelta(days=30), - 'ending_date': timezone.now()+timedelta(days=31), - 'created_by': cls.user1, } - cls.event1 = Event.objects.create(**cls.event1_data) - - def setUp(self): - # Tag - self.tag_root_data = {"name": "tag2", "is_public": False, - "color": "#222"} - self.tag_event_data = {"name": "tag3", "is_public": False, - "color": "#333", 'event': self.event1} - self.tag_root = ActivityTag.objects.create(**self.tag_root_data) - self.tag_event = ActivityTag.objects.create(**self.tag_event_data) - - # Places - self.place_root_data = {'name': "place1", 'event': None, } - self.place_event_data = {'name': "place2", 'event': self.event1, } - self.place_root = Place.objects.create(**self.place_root_data) - self.place_event = Place.objects.create(**self.place_event_data) - - def test_lists(self): - """ - ensure that only root-level models are served under - api/%root_base_name/ - and that root-level and event-level models are served under - api/event//%event_base_name/ - """ - event_id = self.event1.id - root_count = self.model.objects.filter(event=None).count() - event_count = (self.model.objects - .filter(Q(event=self.event1) | Q(event=None)).count()) - - self.client.force_authenticate(user=self.user1) - - url = reverse('{base}-list'.format(base=self.root_base_name)) - response = self.client.get(url, format='json') - self.assertEqual(response.json()['count'], root_count) - - event_id = self.event1.id - url = reverse('{base}-list'.format(base=self.event_base_name), - kwargs={'event_pk': event_id}) - response = self.client.get(url, format='json') - self.assertEqual(response.json()['count'], event_count) - - -# TODO rajouter la gestion des permissions dans le Mixin -# TODO rajouter un test pour s'assurer que les personnes non -# connectées ne peuvent pas create/update/delete -# TODO? essayer de factoriser avec EventBasedMixin ? -# FIXME not working, peut être que le problème vient -# du fait que les dates sont mal envoyées dans le data ? A voir. -class ModelTestMixin(DataBaseMixin): - """ - Note : need to define : `model`, `base_name`, - `instance_name`, `field_tested`, `serializer` - - generic mixin for testing creation/update/delete - of models served by the API - """ - def test_user_create(self): - """ - ensure a permited user can create a new %model object using API - """ - data = getattr(self, self.data_creation) - initial_count = self.model.objects.count() - - url = reverse('{base_name}-list'.format(base_name=self.base_name)) - self.client.force_authenticate(user=self.user1) - response = self.client.post(url, data) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(self.model.objects.count(), initial_count + 1) - - instance = self.model.objects.get(id=initial_count+1) - for field in self.tested_fields.keys(): - self.assertEqual(getattr(instance, field), data[field]) - - def test_user_update(self): - """ - ensure a permited user can update a new %model object using API - """ - instance = getattr(self, self.instance_name) - factory = APIRequestFactory() - - instance_id = instance.id - url = reverse('{base_name}-detail'.format(base_name=self.base_name), - kwargs={'pk': instance_id}) - - request = factory.get(url) - data = self.serializer(instance, context={'request': request}).data - for field, value in self.tested_fields.items(): - data[field] = value - - self.client.force_authenticate(user=self.user1) - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - instance.refresh_from_db() - for field, value in self.tested_fields.items(): - self.assertEqual(getattr(instance, field), value) - - def test_user_delete(self): - """ - ensure a permited user can delete a new %model object using API - """ - instance = getattr(self, self.instance_name) - initial_count = self.model.objects.count() - - instance_id = instance.id - url = reverse('{base_name}-detail'.format(base_name=self.base_name), - kwargs={'pk': instance_id}) - - self.client.force_authenticate(user=self.user1) - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(self.model.objects.count(), initial_count - 1) diff --git a/evenementiel/settings/common.py b/evenementiel/settings/common.py index 8baa801..6d41864 100644 --- a/evenementiel/settings/common.py +++ b/evenementiel/settings/common.py @@ -78,6 +78,7 @@ REST_FRAMEWORK = { 'rest_framework.permissions.AllowAny', ], 'PAGE_SIZE': 10, + 'TEST_REQUEST_DEFAULT_FORMAT': 'json', } ROOT_URLCONF = 'evenementiel.urls'