Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 1 | """Tests for the logfiles REST API.""" |
| 2 | |
| 3 | import os |
Franz-Xaver Geiger | 38a66bc | 2018-10-09 14:52:26 +0200 | [diff] [blame] | 4 | import shutil |
| 5 | import tempfile |
Mitja Nikolaus | c2da285 | 2018-10-04 15:09:11 +0200 | [diff] [blame] | 6 | import zipfile |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 7 | |
Franz-Xaver Geiger | 38a66bc | 2018-10-09 14:52:26 +0200 | [diff] [blame] | 8 | from django.conf import settings |
| 9 | from django.core.files.storage import default_storage |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 10 | from django.db import connection |
Franz-Xaver Geiger | 38a66bc | 2018-10-09 14:52:26 +0200 | [diff] [blame] | 11 | from django.test import override_settings |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 12 | from django.urls import reverse |
| 13 | |
| 14 | from rest_framework import status |
| 15 | |
Mitja Nikolaus | cc90d57 | 2018-11-22 16:40:15 +0100 | [diff] [blame] | 16 | from crashreports.models import ( |
| 17 | crashreport_file_name, |
| 18 | Device, |
| 19 | Crashreport, |
| 20 | LogFile, |
| 21 | ) |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 22 | from crashreports.tests.utils import ( |
| 23 | Dummy, |
| 24 | RaceConditionsTestCase, |
| 25 | HiccupCrashreportsAPITestCase, |
| 26 | ) |
| 27 | |
| 28 | LIST_CREATE_URL = "api_v1_crashreports" |
| 29 | PUT_LOGFILE_URL = "api_v1_putlogfile_for_device_id" |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 30 | |
| 31 | |
Franz-Xaver Geiger | 38a66bc | 2018-10-09 14:52:26 +0200 | [diff] [blame] | 32 | @override_settings(MEDIA_ROOT=tempfile.mkdtemp(".hiccup-tests")) |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 33 | class LogfileUploadTest(HiccupCrashreportsAPITestCase): |
| 34 | """Test cases for upload of log files.""" |
| 35 | |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 36 | # pylint: disable=too-many-ancestors |
| 37 | |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 38 | LIST_CREATE_URL = "api_v1_crashreports" |
| 39 | PUT_LOGFILE_URL = "api_v1_putlogfile_for_device_id" |
Mitja Nikolaus | cc90d57 | 2018-11-22 16:40:15 +0100 | [diff] [blame] | 40 | POST_LOGFILE_URL = "api_v1_logfiles_by_id" |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 41 | |
Mitja Nikolaus | 188bca6 | 2018-10-04 15:17:48 +0200 | [diff] [blame] | 42 | def setUp(self): |
| 43 | """Call the super setup method and register a device.""" |
| 44 | super().setUp() |
| 45 | self.device_uuid, self.user, _ = self._register_device() |
| 46 | |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 47 | def upload_crashreport(self, user, uuid): |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 48 | """ |
| 49 | Upload dummy crashreport data. |
| 50 | |
| 51 | Args: |
| 52 | user: The user which should be used for uploading the report |
| 53 | uuid: The uuid of the device to which the report should be uploaded |
| 54 | |
| 55 | Returns: The local id of the device for which the report was uploaded. |
| 56 | |
| 57 | """ |
| 58 | data = Dummy.crashreport_data(uuid=uuid) |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 59 | response = user.post(reverse(LIST_CREATE_URL), data) |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 60 | self.assertEqual(status.HTTP_201_CREATED, response.status_code) |
| 61 | self.assertTrue("device_local_id" in response.data) |
| 62 | device_local_id = response.data["device_local_id"] |
| 63 | |
| 64 | return device_local_id |
| 65 | |
Mitja Nikolaus | c2da285 | 2018-10-04 15:09:11 +0200 | [diff] [blame] | 66 | def _assert_zip_file_contents_equal(self, file1, file2): |
| 67 | """Assert that the files within two zip files are equal.""" |
| 68 | zip_file_1 = zipfile.ZipFile(file1) |
| 69 | zip_file_2 = zipfile.ZipFile(file2) |
| 70 | for file_name_1, file_name_2 in zip( |
| 71 | zip_file_1.filelist, zip_file_2.filelist |
| 72 | ): |
| 73 | file_1 = zip_file_1.open(file_name_1) |
| 74 | file_2 = zip_file_2.open(file_name_2) |
| 75 | |
| 76 | self.assertEqual(file_1.read(), file_2.read()) |
| 77 | |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 78 | def upload_logfile(self, client, uuid, device_local_id): |
| 79 | """Upload a log file and assert that it was created.""" |
Mitja Nikolaus | 77dd565 | 2018-12-06 11:27:01 +0100 | [diff] [blame^] | 80 | logfile = open(Dummy.DEFAULT_LOG_FILE_PATHS[0], "rb") |
Mitja Nikolaus | c2da285 | 2018-10-04 15:09:11 +0200 | [diff] [blame] | 81 | logfile_name = os.path.basename(logfile.name) |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 82 | response = client.post( |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 83 | reverse( |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 84 | PUT_LOGFILE_URL, args=[uuid, device_local_id, logfile_name] |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 85 | ), |
| 86 | {"file": logfile}, |
| 87 | format="multipart", |
| 88 | ) |
Mitja Nikolaus | 6e11847 | 2018-10-04 11:15:29 +0200 | [diff] [blame] | 89 | logfile.close() |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 90 | self.assertEqual(status.HTTP_201_CREATED, response.status_code) |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 91 | return response |
| 92 | |
| 93 | def _test_logfile_upload(self, user, uuid): |
| 94 | # Upload crashreport |
| 95 | device_local_id = self.upload_crashreport(user, uuid) |
| 96 | |
| 97 | # Upload a logfile for the crashreport |
| 98 | self.upload_logfile(user, uuid, device_local_id) |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 99 | |
Mitja Nikolaus | c2da285 | 2018-10-04 15:09:11 +0200 | [diff] [blame] | 100 | logfile_instance = ( |
| 101 | Device.objects.get(uuid=uuid) |
| 102 | .crashreports.get(device_local_id=device_local_id) |
| 103 | .logfiles.last() |
| 104 | ) |
| 105 | uploaded_logfile_path = crashreport_file_name( |
Mitja Nikolaus | 77dd565 | 2018-12-06 11:27:01 +0100 | [diff] [blame^] | 106 | logfile_instance, os.path.basename(Dummy.DEFAULT_LOG_FILE_PATHS[0]) |
Mitja Nikolaus | c2da285 | 2018-10-04 15:09:11 +0200 | [diff] [blame] | 107 | ) |
| 108 | |
Franz-Xaver Geiger | 38a66bc | 2018-10-09 14:52:26 +0200 | [diff] [blame] | 109 | self.assertTrue(default_storage.exists(uploaded_logfile_path)) |
Mitja Nikolaus | c2da285 | 2018-10-04 15:09:11 +0200 | [diff] [blame] | 110 | # The files are not 100% equal, because the server adds some extra |
| 111 | # bytes. However, we mainly care that the contents are equal: |
| 112 | self._assert_zip_file_contents_equal( |
Franz-Xaver Geiger | 38a66bc | 2018-10-09 14:52:26 +0200 | [diff] [blame] | 113 | default_storage.path(uploaded_logfile_path), |
Mitja Nikolaus | 77dd565 | 2018-12-06 11:27:01 +0100 | [diff] [blame^] | 114 | Dummy.DEFAULT_LOG_FILE_PATHS[0], |
Mitja Nikolaus | c2da285 | 2018-10-04 15:09:11 +0200 | [diff] [blame] | 115 | ) |
| 116 | |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 117 | def test_logfile_upload_as_user(self): |
| 118 | """Test upload of logfiles as device owner.""" |
Mitja Nikolaus | 188bca6 | 2018-10-04 15:17:48 +0200 | [diff] [blame] | 119 | self._test_logfile_upload(self.user, self.device_uuid) |
Mitja Nikolaus | 03e412b | 2018-09-18 17:50:15 +0200 | [diff] [blame] | 120 | |
Mitja Nikolaus | e0e8377 | 2018-11-05 10:00:53 +0100 | [diff] [blame] | 121 | def test_logfile_upload_as_fp_staff(self): |
| 122 | """Test upload of logfiles as Fairphone staff user.""" |
| 123 | self._test_logfile_upload(self.fp_staff_client, self.device_uuid) |
Mitja Nikolaus | 188bca6 | 2018-10-04 15:17:48 +0200 | [diff] [blame] | 124 | |
Mitja Nikolaus | cc90d57 | 2018-11-22 16:40:15 +0100 | [diff] [blame] | 125 | def test_logfile_deletion(self): |
| 126 | """Test deletion of logfile instances.""" |
| 127 | # Create a user, device and crashreport with logfile |
Mitja Nikolaus | 77dd565 | 2018-12-06 11:27:01 +0100 | [diff] [blame^] | 128 | device = Dummy.create_device(Dummy.create_user()) |
| 129 | crashreport = Dummy.create_report(Crashreport, device) |
| 130 | logfile, logfile_path = Dummy.create_log_file_with_actual_file( |
Mitja Nikolaus | cc90d57 | 2018-11-22 16:40:15 +0100 | [diff] [blame] | 131 | crashreport |
| 132 | ) |
| 133 | |
| 134 | # Assert that the crashreport and logfile have been created |
| 135 | self.assertEqual(Crashreport.objects.count(), 1) |
| 136 | self.assertEqual(LogFile.objects.count(), 1) |
| 137 | self.assertTrue(os.path.isfile(logfile_path)) |
| 138 | |
| 139 | # Delete the logfile |
| 140 | response = self.fp_staff_client.delete( |
| 141 | reverse(self.POST_LOGFILE_URL, args=[logfile.id]) |
| 142 | ) |
| 143 | self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code) |
| 144 | |
| 145 | # Assert that the logfile has been deleted |
| 146 | self.assertEqual(LogFile.objects.count(), 0) |
| 147 | self.assertFalse(os.path.isfile(logfile_path)) |
| 148 | |
Mitja Nikolaus | 188bca6 | 2018-10-04 15:17:48 +0200 | [diff] [blame] | 149 | def tearDown(self): |
| 150 | """Remove the file and directories that were created for the test.""" |
Franz-Xaver Geiger | 38a66bc | 2018-10-09 14:52:26 +0200 | [diff] [blame] | 151 | shutil.rmtree(settings.MEDIA_ROOT) |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 152 | |
| 153 | |
Mitja Nikolaus | bc03e68 | 2018-11-13 16:48:20 +0100 | [diff] [blame] | 154 | class LogfileRaceConditionsTestCase(RaceConditionsTestCase): |
| 155 | """Test cases for logfile race conditions.""" |
| 156 | |
| 157 | def test_create_multiple_logfiles(self): |
| 158 | """Test that no race condition occurs when creating logfiles.""" |
| 159 | uuid, user, _ = self._register_device() |
| 160 | device_local_id = LogfileUploadTest.upload_crashreport(self, user, uuid) |
| 161 | |
| 162 | def upload_logfile(client, uuid, device_local_id): |
| 163 | LogfileUploadTest.upload_logfile( |
| 164 | self, client, uuid, device_local_id |
| 165 | ) |
| 166 | connection.close() |
| 167 | |
| 168 | argslist = [[user, uuid, device_local_id] for _ in range(10)] |
| 169 | |
| 170 | self._test_create_multiple( |
| 171 | LogFile, upload_logfile, argslist, "crashreport_local_id" |
| 172 | ) |