From 1233f3ea16a04e3d400fdce56f02376fa5a389e0 Mon Sep 17 00:00:00 2001 From: Lina Savova Date: Tue, 5 Jul 2022 18:14:33 +0300 Subject: [PATCH 1/6] Add a test for the standard file upload --- .../files/tests/flows/test_standard_upload.py | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/styleguide_example/files/tests/flows/test_standard_upload.py b/styleguide_example/files/tests/flows/test_standard_upload.py index a096f6d0..bb9ae0c0 100644 --- a/styleguide_example/files/tests/flows/test_standard_upload.py +++ b/styleguide_example/files/tests/flows/test_standard_upload.py @@ -1,4 +1,14 @@ -from django.test import TestCase +from django.conf import settings +from django.test import TestCase, 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 +16,79 @@ 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): + file_max_size = settings.FILE_MAX_SIZE + + self.assertEqual(0, File.objects.count()) + self.assertEqual(0, BaseUser.objects.count()) + + # Create a user + credentials = { + "email": "some_email@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=b"Test", 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}, enctype="multipart/form-data", **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=(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}, enctype="multipart/form-data", **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=file_max_size * "b".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}, enctype="multipart/form-data", **auth_headers + ) + + self.assertEqual(201, response.status_code) + self.assertEqual(2, File.objects.count()) class StandardUploadAdminTests(TestCase): From bf67c5703f4f3f57f5a8bba4f499c22ea0757a38 Mon Sep 17 00:00:00 2001 From: Lina Savova Date: Wed, 20 Jul 2022 13:22:42 +0300 Subject: [PATCH 2/6] Add a test for the Django admin file upload --- .../files/tests/flows/test_standard_upload.py | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/styleguide_example/files/tests/flows/test_standard_upload.py b/styleguide_example/files/tests/flows/test_standard_upload.py index bb9ae0c0..9ad797c4 100644 --- a/styleguide_example/files/tests/flows/test_standard_upload.py +++ b/styleguide_example/files/tests/flows/test_standard_upload.py @@ -105,3 +105,103 @@ 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 = APIClient() + + 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): + file_max_size = settings.FILE_MAX_SIZE + + self.assertEqual(0, File.objects.count()) + + # Create a superuser + credentials = { + "email": "admin_email@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=b"Test!", 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(self.admin_files_list_url, response.url) + 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=(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.assertRedirects(response, self.admin_files_list_url) + 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=(file_max_size + 10) * "b".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) + response_2 = self.client.get(response.url) + + self.assertContains(response_2, "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=(file_max_size + 20) * "c".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) + response_2 = self.client.get(response.url) + + self.assertContains(response_2, "File is too large") + self.assertEqual(1, File.objects.count()) + self.assertEqual(file_2.name, File.objects.last().original_file_name) From 39df66010720abad10ecd78e55671ad42f409e94 Mon Sep 17 00:00:00 2001 From: Lina Savova Date: Wed, 20 Jul 2022 18:07:50 +0300 Subject: [PATCH 3/6] Code improvements --- .../files/tests/flows/test_standard_upload.py | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/styleguide_example/files/tests/flows/test_standard_upload.py b/styleguide_example/files/tests/flows/test_standard_upload.py index 9ad797c4..50035dea 100644 --- a/styleguide_example/files/tests/flows/test_standard_upload.py +++ b/styleguide_example/files/tests/flows/test_standard_upload.py @@ -1,3 +1,4 @@ +import shutil from django.conf import settings from django.test import TestCase, override_settings from django.urls import reverse @@ -27,14 +28,13 @@ def setUp(self): @override_settings(FILE_MAX_SIZE=10) def test_standard_upload(self): - file_max_size = settings.FILE_MAX_SIZE self.assertEqual(0, File.objects.count()) self.assertEqual(0, BaseUser.objects.count()) # Create a user credentials = { - "email": "some_email@hacksoft.io", + "email": "test@hacksoft.io", "password": "123456" } user_create(**credentials) @@ -53,43 +53,46 @@ def test_standard_upload(self): # Create a small sized file file_1 = SimpleUploadedFile( - name="file_small.txt", content=b"Test", content_type="text/plain" + 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}, enctype="multipart/form-data", **auth_headers - ) + 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=(file_max_size + 1) * "a".encode(), content_type="text/plain" + 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}, enctype="multipart/form-data", **auth_headers - ) + 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=file_max_size * "b".encode(), content_type="text/plain" + 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}, enctype="multipart/form-data", **auth_headers - ) + 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): """ @@ -117,13 +120,12 @@ def setUp(self): @override_settings(FILE_MAX_SIZE=10) def test_standard_admin_upload_and_update(self): - file_max_size = settings.FILE_MAX_SIZE self.assertEqual(0, File.objects.count()) # Create a superuser credentials = { - "email": "admin_email@hacksoft.io", + "email": "test@hacksoft.io", "password": "123456", "is_admin": True, "is_superuser": True @@ -133,7 +135,9 @@ def test_standard_admin_upload_and_update(self): self.assertEqual(1, BaseUser.objects.count()) file_1 = SimpleUploadedFile( - name="first_file.txt", content=b"Test!", content_type="text/plain" + name="first_file.txt", + content=(settings.FILE_MAX_SIZE - 5) * "a".encode(), + content_type="text/plain" ) data_file_1 = { @@ -149,12 +153,13 @@ def test_standard_admin_upload_and_update(self): successfully_uploaded_file = File.objects.last() self.assertEqual(302, response.status_code) - self.assertEqual(self.admin_files_list_url, response.url) 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=(file_max_size - 1) * "a".encode(), content_type="text/plain" + name="second_file.txt", + content=(settings.FILE_MAX_SIZE - 1) * "a".encode(), + content_type="text/plain" ) data_file_2 = { @@ -166,12 +171,13 @@ def test_standard_admin_upload_and_update(self): response = self.client.post(self.admin_update_file_url(successfully_uploaded_file), data_file_2) self.assertEqual(302, response.status_code) - self.assertRedirects(response, self.admin_files_list_url) 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=(file_max_size + 10) * "b".encode(), content_type="text/plain" + name="oversized_file.txt", + content=(settings.FILE_MAX_SIZE + 1) * "a".encode(), + content_type="text/plain" ) data_oversized_file = { @@ -180,15 +186,16 @@ def test_standard_admin_upload_and_update(self): } 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) - response_2 = self.client.get(response.url) + response = self.client.post(self.admin_upload_file_url, data_oversized_file, follow=True) - self.assertContains(response_2, "File is too large") + 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=(file_max_size + 20) * "c".encode(), content_type="text/plain" + name="new_oversized_file.txt", + content=(settings.FILE_MAX_SIZE + 1) * "a".encode(), + content_type="text/plain" ) data_new_oversized_file = { @@ -199,9 +206,13 @@ def test_standard_admin_upload_and_update(self): 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) - response_2 = self.client.get(response.url) + response = self.client.post( + self.admin_update_file_url(File.objects.last()), data_new_oversized_file, follow=True + ) - self.assertContains(response_2, "File is too large") + 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) From b7d0312a2dcf26f84d0802532ae05425570d0243 Mon Sep 17 00:00:00 2001 From: LinaSSavova Date: Thu, 21 Jul 2022 12:05:49 +0300 Subject: [PATCH 4/6] Initial direct upload test --- .../files/tests/flows/test_direct_upload.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/styleguide_example/files/tests/flows/test_direct_upload.py b/styleguide_example/files/tests/flows/test_direct_upload.py index 192f4804..8a2772df 100644 --- a/styleguide_example/files/tests/flows/test_direct_upload.py +++ b/styleguide_example/files/tests/flows/test_direct_upload.py @@ -1,4 +1,7 @@ from django.test import TestCase +from django.urls import reverse + +from rest_framework.test import APIClient class DirectUploadApiTests(TestCase): @@ -7,3 +10,15 @@ class DirectUploadApiTests(TestCase): 1. A start-upload-finish cycle, where we patch the presign generation with local upload storage. """ + def setUp(self): + self.client = APIClient() + + 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)} + ) + + def test_direct_upload(self): + pass From d77c13478fede2c2ba556f60eb5e3898f5257c15 Mon Sep 17 00:00:00 2001 From: LinaSSavova Date: Tue, 16 Aug 2022 15:55:22 +0300 Subject: [PATCH 5/6] Add a test for the direct file upload --- .../files/tests/flows/test_direct_upload.py | 85 +++++++++++++++++-- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/styleguide_example/files/tests/flows/test_direct_upload.py b/styleguide_example/files/tests/flows/test_direct_upload.py index 8a2772df..fbd8c803 100644 --- a/styleguide_example/files/tests/flows/test_direct_upload.py +++ b/styleguide_example/files/tests/flows/test_direct_upload.py @@ -1,18 +1,24 @@ -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 -class DirectUploadApiTests(TestCase): - """ - We want to test the following: +from styleguide_example.files.models import File + +from styleguide_example.users.services import user_create + +from styleguide_example.files.enums import FileUploadStorage - 1. A start-upload-finish cycle, where we patch the presign generation with local upload storage. - """ + +class DirectUploadApiTests(TestCase): 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( @@ -20,5 +26,70 @@ def setUp(self): kwargs={"file_id": str(file.id)} ) + @override_settings(FILE_UPLOAD_STORAGE=FileUploadStorage.S3, FILE_MAX_SIZE=10) def test_direct_upload(self): - pass + """ + 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}) + + self.assertEqual(200, response.status_code) + self.assertIsNotNone(File.objects.last().upload_finished_at) From 8257343916bd94b68ba86af6f40ddf39ed1a5865 Mon Sep 17 00:00:00 2001 From: LinaSSavova Date: Thu, 18 Aug 2022 10:17:53 +0300 Subject: [PATCH 6/6] Change the client when working with the admin --- styleguide_example/files/tests/flows/test_standard_upload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styleguide_example/files/tests/flows/test_standard_upload.py b/styleguide_example/files/tests/flows/test_standard_upload.py index 50035dea..11d94737 100644 --- a/styleguide_example/files/tests/flows/test_standard_upload.py +++ b/styleguide_example/files/tests/flows/test_standard_upload.py @@ -1,6 +1,6 @@ import shutil from django.conf import settings -from django.test import TestCase, override_settings +from django.test import TestCase, Client, override_settings from django.urls import reverse from django.core.files.uploadedfile import SimpleUploadedFile @@ -109,7 +109,7 @@ class StandardUploadAdminTests(TestCase): 2. Update an existing fila via the Django admin, assert error, nothing gets created. """ def setUp(self): - self.client = APIClient() + self.client = Client() self.admin_upload_file_url = reverse("admin:files_file_add") self.admin_files_list_url = reverse("admin:files_file_changelist")