diff --git a/styleguide_example/files/tests/flows/test_direct_upload.py b/styleguide_example/files/tests/flows/test_direct_upload.py index 192f4804..fbd8c803 100644 --- a/styleguide_example/files/tests/flows/test_direct_upload.py +++ b/styleguide_example/files/tests/flows/test_direct_upload.py @@ -1,9 +1,95 @@ -from django.test import TestCase +from django.test import TestCase, override_settings +from django.urls import reverse +from django.conf import settings +from django.core.files.uploadedfile import SimpleUploadedFile + +from rest_framework.test import APIClient + +from unittest import mock + +from styleguide_example.files.models import File + +from styleguide_example.users.services import user_create + +from styleguide_example.files.enums import FileUploadStorage class DirectUploadApiTests(TestCase): - """ - We want to test the following: + def setUp(self): + self.client = APIClient() + + self.jwt_login_url = reverse("api:authentication:jwt:login") + self.direct_upload_start_url = reverse("api:files:upload:direct:start") + self.direct_upload_finish_url = reverse("api:files:upload:direct:finish") + self.direct_upload_local_url = lambda file: reverse( + "api:files:upload:direct:local", + kwargs={"file_id": str(file.id)} + ) + + @override_settings(FILE_UPLOAD_STORAGE=FileUploadStorage.S3, FILE_MAX_SIZE=10) + def test_direct_upload(self): + """ + 1. Get presigned_post_url from the direct_upload_start endpoint + 1.1. to mock generate the presigned post + + Assert the presigned data + Assert that the file object is created + + 2. Call the finish endpoint and assert that the file is marked as uploaded + + """ + credentials = { + "email": "test@hacksoft.io", + "password": "123456" + } + user_create(**credentials) + + response = self.client.post(self.jwt_login_url, credentials) + + self.assertEqual(200, response.status_code) + + token = response.data["token"] + auth_headers = { + "HTTP_AUTHORIZATION": f"{settings.JWT_AUTH['JWT_AUTH_HEADER_PREFIX']} {token}" + } + + file_1 = SimpleUploadedFile( + name="file_small.txt", + content=(settings.FILE_MAX_SIZE - 5) * "a".encode(), + content_type="text/plain" + ) + + file_data = { + "file_name": file_1.name, + "file_type": file_1.content_type + } + + presigned_url = "test_presigned_url" + + presigned_data = { + "url": presigned_url, + } + + with self.subTest("1. Get presigned_post_url from the direct_upload_start endpoint"): + + self.assertEqual(0, File.objects.count()) + + with mock.patch( + "styleguide_example.files.services.s3_generate_presigned_post" + ) as s3_generate_presigned_post_mock: + s3_generate_presigned_post_mock.return_value = {**presigned_data} + + response = self.client.post(self.direct_upload_start_url, file_data, **auth_headers) + file_id = response.data["id"] + + self.assertEqual(200, response.status_code) + self.assertEqual(presigned_url, response.data["url"]) + self.assertTrue(s3_generate_presigned_post_mock.called) + self.assertEqual(1, File.objects.count()) + self.assertIsNone(File.objects.last().upload_finished_at) + + with self.subTest("2. Call the finish endpoint and assert that the file is marked as uploaded"): + response = self.client.post(self.direct_upload_finish_url, {"file_id": file_id}) - 1. A start-upload-finish cycle, where we patch the presign generation with local upload storage. - """ + self.assertEqual(200, response.status_code) + self.assertIsNotNone(File.objects.last().upload_finished_at) diff --git a/styleguide_example/files/tests/flows/test_standard_upload.py b/styleguide_example/files/tests/flows/test_standard_upload.py index a096f6d0..11d94737 100644 --- a/styleguide_example/files/tests/flows/test_standard_upload.py +++ b/styleguide_example/files/tests/flows/test_standard_upload.py @@ -1,4 +1,15 @@ -from django.test import TestCase +import shutil +from django.conf import settings +from django.test import TestCase, Client, override_settings +from django.urls import reverse +from django.core.files.uploadedfile import SimpleUploadedFile + +from rest_framework.test import APIClient + +from styleguide_example.files.models import File + +from styleguide_example.users.models import BaseUser +from styleguide_example.users.services import user_create class StandardUploadApiTests(TestCase): @@ -6,8 +17,81 @@ class StandardUploadApiTests(TestCase): We want to test the following general cases: 1. Upload a file, below the size limit, assert models gets created accordingly. - 1. Upload a file, above the size limit (patch settings), assert API error, nothing gets created. + 2. Upload a file, above the size limit (patch settings), assert API error, nothing gets created. + 3. Upload a file, equal to the size limit, assert models gets created accordingly. """ + def setUp(self): + self.client = APIClient() + + self.jwt_login_url = reverse("api:authentication:jwt:login") + self.standard_upload_url = reverse("api:files:upload:standard") + + @override_settings(FILE_MAX_SIZE=10) + def test_standard_upload(self): + + self.assertEqual(0, File.objects.count()) + self.assertEqual(0, BaseUser.objects.count()) + + # Create a user + credentials = { + "email": "test@hacksoft.io", + "password": "123456" + } + user_create(**credentials) + + self.assertEqual(1, BaseUser.objects.count()) + + # Log in and get the authorization data needed + response = self.client.post(self.jwt_login_url, credentials) + + self.assertEqual(200, response.status_code) + + token = response.data["token"] + auth_headers = { + "HTTP_AUTHORIZATION": f"{settings.JWT_AUTH['JWT_AUTH_HEADER_PREFIX']} {token}" + } + + # Create a small sized file + file_1 = SimpleUploadedFile( + name="file_small.txt", + content=(settings.FILE_MAX_SIZE - 5) * "a".encode(), + content_type="text/plain" + ) + + with self.subTest("1. Upload a file, below the size limit, assert models gets created accordingly"): + response = self.client.post(self.standard_upload_url, {"file": file_1}, **auth_headers) + + self.assertEqual(201, response.status_code) + self.assertEqual(1, File.objects.count()) + + # Create a file above the size limit + file_2 = SimpleUploadedFile( + name="file_big.txt", + content=(settings.FILE_MAX_SIZE + 1) * "a".encode(), + content_type="text/plain" + ) + + with self.subTest("2. Upload a file, above the size limit, assert API error, nothing gets created"): + response = self.client.post(self.standard_upload_url, {"file": file_2}, **auth_headers) + + self.assertEqual(400, response.status_code) + self.assertEqual(1, File.objects.count()) + + # Create a file equal to the size limit + file_3 = SimpleUploadedFile( + name="file_equal.txt", + content=settings.FILE_MAX_SIZE * "a".encode(), + content_type="text/plain" + ) + + with self.subTest("3. Upload a file, equal to the size limit, assert models gets created accordingly"): + response = self.client.post(self.standard_upload_url, {"file": file_3}, **auth_headers) + + self.assertEqual(201, response.status_code) + self.assertEqual(2, File.objects.count()) + + def tearDown(self): + shutil.rmtree(settings.MEDIA_ROOT, ignore_errors=True) class StandardUploadAdminTests(TestCase): @@ -24,3 +108,111 @@ class StandardUploadAdminTests(TestCase): 1. Create a new file via the Django admin, assert error, nothing gets created. 2. Update an existing fila via the Django admin, assert error, nothing gets created. """ + def setUp(self): + self.client = Client() + + self.admin_upload_file_url = reverse("admin:files_file_add") + self.admin_files_list_url = reverse("admin:files_file_changelist") + self.admin_update_file_url = lambda file: reverse( + "admin:files_file_change", + kwargs={"object_id": str(file.id)} + ) + + @override_settings(FILE_MAX_SIZE=10) + def test_standard_admin_upload_and_update(self): + + self.assertEqual(0, File.objects.count()) + + # Create a superuser + credentials = { + "email": "test@hacksoft.io", + "password": "123456", + "is_admin": True, + "is_superuser": True + } + user = BaseUser.objects.create(**credentials) + + self.assertEqual(1, BaseUser.objects.count()) + + file_1 = SimpleUploadedFile( + name="first_file.txt", + content=(settings.FILE_MAX_SIZE - 5) * "a".encode(), + content_type="text/plain" + ) + + data_file_1 = { + "file": file_1, + "uploaded_by": user.id + } + + # Log in with the superuser account + self.client.force_login(user) + + with self.subTest("1. Create a new file via the Django admin, assert everything gets created"): + response = self.client.post(self.admin_upload_file_url, data_file_1) + successfully_uploaded_file = File.objects.last() + + self.assertEqual(302, response.status_code) + self.assertEqual(1, File.objects.count()) + self.assertEqual(file_1.name, successfully_uploaded_file.original_file_name) + + file_2 = SimpleUploadedFile( + name="second_file.txt", + content=(settings.FILE_MAX_SIZE - 1) * "a".encode(), + content_type="text/plain" + ) + + data_file_2 = { + "file": file_2, + "uploaded_by": user.id + } + + with self.subTest("2. Update an existing file via the Django admin, assert everything gets updated"): + response = self.client.post(self.admin_update_file_url(successfully_uploaded_file), data_file_2) + + self.assertEqual(302, response.status_code) + self.assertEqual(1, File.objects.count()) + self.assertEqual(file_2.name, File.objects.last().original_file_name) + + file_3 = SimpleUploadedFile( + name="oversized_file.txt", + content=(settings.FILE_MAX_SIZE + 1) * "a".encode(), + content_type="text/plain" + ) + + data_oversized_file = { + "file": file_3, + "uploaded_by": user.id + } + + with self.subTest("3. Create a new oversized file via the Django admin, assert error, nothing gets created"): + response = self.client.post(self.admin_upload_file_url, data_oversized_file, follow=True) + + self.assertContains(response, "File is too large") + self.assertEqual(1, File.objects.count()) + self.assertEqual(file_2.name, File.objects.last().original_file_name) + + file_4 = SimpleUploadedFile( + name="new_oversized_file.txt", + content=(settings.FILE_MAX_SIZE + 1) * "a".encode(), + content_type="text/plain" + ) + + data_new_oversized_file = { + "file": file_4, + "uploaded_by": user.id + } + + with self.subTest( + "4. Update an existing file with an oversized one via the Django admin, assert error, nothing gets created" + ): + response = self.client.post( + self.admin_update_file_url(File.objects.last()), data_new_oversized_file, follow=True + ) + + self.assertContains(response, "File is too large") + self.assertEqual(1, File.objects.count()) + self.assertEqual(file_2.name, File.objects.last().original_file_name) + + def tearDown(self): + shutil.rmtree(settings.MEDIA_ROOT, ignore_errors=True)