import urllib from django.core.urlresolvers import reverse from rest_framework.test import APITestCase from experiments.factories import ExperimentDataFactory, ExperimentKeyValueFactory from experiments.models import ExperimentData, ExperimentKeyValue from experiments.serializers import ExperimentDataSerializer from student.tests.factories import UserFactory class ExperimentDataViewSetTests(APITestCase): def assert_data_created_for_user(self, user, method='post', status=201): url = reverse('api_experiments:v0:data-list') data = { 'experiment_id': 1, 'key': 'foo', 'value': 'bar', } self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) response = getattr(self.client, method)(url, data) self.assertEqual(response.status_code, status) # This will raise an exception if no data exists ExperimentData.objects.get(user=user) data['user'] = user.username self.assertDictContainsSubset(data, response.data) def test_list_permissions(self): """ Users should only be able to list their own data. """ url = reverse('api_experiments:v0:data-list') user = UserFactory() response = self.client.get(url) self.assertEqual(response.status_code, 401) ExperimentDataFactory() datum = ExperimentDataFactory(user=user) self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['results'], ExperimentDataSerializer([datum], many=True).data) def test_list_filtering(self): """ Users should be able to filter by the experiment_id and key fields. """ url = reverse('api_experiments:v0:data-list') user = UserFactory() self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) experiment_id = 1 ExperimentDataFactory() ExperimentDataFactory(user=user) data = ExperimentDataFactory.create_batch(3, user=user, experiment_id=experiment_id) qs = urllib.urlencode({'experiment_id': experiment_id}) response = self.client.get('{url}?{qs}'.format(url=url, qs=qs)) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['results'], ExperimentDataSerializer(data, many=True).data) datum = data[0] qs = urllib.urlencode({'key': datum.key}) response = self.client.get('{url}?{qs}'.format(url=url, qs=qs)) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['results'], ExperimentDataSerializer([datum], many=True).data) qs = urllib.urlencode({'experiment_id': experiment_id, 'key': datum.key}) response = self.client.get('{url}?{qs}'.format(url=url, qs=qs)) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['results'], ExperimentDataSerializer([datum], many=True).data) def test_read_permissions(self): """ Users should only be allowed to read their own data. """ user = UserFactory() datum = ExperimentDataFactory(user=user) url = reverse('api_experiments:v0:data-detail', kwargs={'pk': datum.id}) response = self.client.get(url) self.assertEqual(response.status_code, 401) self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) response = self.client.get(url) self.assertEqual(response.status_code, 200) other_user = UserFactory() self.client.login(username=other_user.username, password=UserFactory._DEFAULT_PASSWORD) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_create_permissions(self): """ Users should only be allowed to create data for themselves. """ url = reverse('api_experiments:v0:data-list') # Authentication is required response = self.client.post(url, {}) self.assertEqual(response.status_code, 401) user = UserFactory() data = { 'experiment_id': 1, 'key': 'foo', 'value': 'bar', } self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) # Users can create data for themselves response = self.client.post(url, data) self.assertEqual(response.status_code, 201) ExperimentData.objects.get(user=user) # A non-staff user cannot create data for another user other_user = UserFactory() data['user'] = other_user.username response = self.client.post(url, data) self.assertEqual(response.status_code, 403) self.assertFalse(ExperimentData.objects.filter(user=other_user).exists()) # A staff user can create data for other users user.is_staff = True user.save() response = self.client.post(url, data) self.assertEqual(response.status_code, 201) ExperimentData.objects.get(user=other_user) def test_put_as_create(self): """ Users should be able to use PUT to create new data. """ user = UserFactory() self.assert_data_created_for_user(user, 'put') # Subsequent requests should update the data self.assert_data_created_for_user(user, 'put', 200) def test_update_permissions(self): """ Users should only be allowed to update their own data. """ user = UserFactory() other_user = UserFactory() datum = ExperimentDataFactory(user=user) url = reverse('api_experiments:v0:data-detail', kwargs={'pk': datum.id}) data = {} response = self.client.patch(url, data) self.assertEqual(response.status_code, 401) self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) response = self.client.patch(url, data) self.assertEqual(response.status_code, 200) self.client.login(username=other_user.username, password=UserFactory._DEFAULT_PASSWORD) response = self.client.patch(url, data) self.assertEqual(response.status_code, 404) def test_bulk_upsert_permissions(self): """ Only staff users can access the bulk upsert endpoint. """ url = reverse('api_experiments:v0:data-bulk-upsert') data = [] # Authentication is required response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 401) user = UserFactory() self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) # No access to non-staff users response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 403) user.is_staff = True user.save() response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 200) def test_bulk_upsert(self): """ The endpoint should support creating/updating multiple ExperimentData objects with a single call. """ url = reverse('api_experiments:v0:data-bulk-upsert') experiment_id = 1 user = UserFactory(is_staff=True) other_user = UserFactory() self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) data = [ { 'experiment_id': experiment_id, 'key': 'foo', 'value': 'bar', 'user': user.username, }, { 'experiment_id': experiment_id, 'key': 'foo', 'value': 'bar', 'user': other_user.username, }, ] response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 200) kwargs = { 'experiment_id': experiment_id, 'key': 'foo', 'value': 'bar', } ExperimentData.objects.get(user=user, **kwargs) ExperimentData.objects.get(user=other_user, **kwargs) # Subsequent calls should update the existing data rather than create more response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 200) ExperimentData.objects.get(user=user, **kwargs) ExperimentData.objects.get(user=other_user, **kwargs) class ExperimentKeyValueViewSetTests(APITestCase): def test_permissions(self): """ Staff access is required for write operations. """ url = reverse('api_experiments:v0:key_value-list') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, {}) self.assertEqual(response.status_code, 401) instance = ExperimentKeyValueFactory() url = reverse('api_experiments:v0:key_value-detail', kwargs={'pk': instance.id}) response = self.client.get(url) self.assertEqual(response.status_code, 200) user = UserFactory(is_staff=False) self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) response = self.client.put(url, {}) self.assertEqual(response.status_code, 403) response = self.client.patch(url, {}) self.assertEqual(response.status_code, 403) response = self.client.delete(url) self.assertEqual(response.status_code, 403) def test_bulk_upsert_permissions(self): """ Non-staff users should not be allowed to access the endpoint. """ data = [] url = reverse('api_experiments:v0:key_value-bulk-upsert') user = UserFactory(is_staff=False) # Authentication required response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 401) # Staff permission required self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 403) def test_bulk_upsert(self): """ The endpoint should support creating/updating multiple ExperimentData objects with a single call. """ url = reverse('api_experiments:v0:key_value-bulk-upsert') experiment_id = 1 user = UserFactory(is_staff=True) data = [ { 'experiment_id': experiment_id, 'key': 'foo', 'value': 'bar', }, { 'experiment_id': experiment_id, 'key': 'foo1', 'value': 'bar', }, ] self.client.login(username=user.username, password=UserFactory._DEFAULT_PASSWORD) # New data should be created response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 200) kwargs = { 'experiment_id': experiment_id, 'value': 'bar', } ExperimentKeyValue.objects.get(key='foo', **kwargs) ExperimentKeyValue.objects.get(key='foo1', **kwargs) # Subsequent calls should update the existing data rather than create more response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, 200) ExperimentKeyValue.objects.get(key='foo', **kwargs) ExperimentKeyValue.objects.get(key='foo1', **kwargs)