blob: 354b33081d40ea8a83329682201be019e9b84121 [file] [log] [blame]
Mitja Nikolaus35181602018-08-03 13:55:30 +02001"""Test the API for crashreports, devices, heartbeats and logfiles."""
Franz-Xaver Geiger4d022d52018-03-20 13:12:49 +01002import os
3import tempfile
4
Dirk Vogtc9e10ab2016-10-12 13:58:15 +02005from django.contrib.auth.models import User
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +01006from django.urls import reverse
Mitja Nikolaus35181602018-08-03 13:55:30 +02007
Dirk Vogt36635692016-10-17 12:19:10 +02008from rest_framework import status
Mitja Nikolaus35181602018-08-03 13:55:30 +02009from rest_framework.test import APIClient, APITestCase
Dirk Vogtf130c752016-08-23 14:45:01 +020010
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +040011from crashreports.models import Crashreport
12
13
14class InvalidCrashTypeError(BaseException):
Mitja Nikolaus35181602018-08-03 13:55:30 +020015 """Invalid crash type encountered.
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +040016
17 The valid crash type values (strings) are:
18 - 'crash';
19 - 'smpl';
20 - 'other'.
21
22 Args:
23 - crash_type: The invalid crash type.
24 """
25
26 def __init__(self, crash_type):
Mitja Nikolaus35181602018-08-03 13:55:30 +020027 """Initialise the exception using the crash type to build a message.
28
29 Args:
30 crash_type: The invalid crash type.
31 """
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +040032 super(InvalidCrashTypeError, self).__init__(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020033 "{} is not a valid crash type".format(crash_type)
34 )
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +040035
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040036
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020037class Dummy:
Mitja Nikolaus35181602018-08-03 13:55:30 +020038 """Dummy values for devices, heartbeats and crashreports."""
39
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040040 DEFAULT_DUMMY_DEVICE_REGISTER_VALUES = {
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020041 "board_date": "2015-12-15T01:23:45Z",
42 "chipset": "Qualcomm MSM8974PRO-AA",
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040043 }
44
45 DEFAULT_DUMMY_HEARTBEAT_VALUES = {
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020046 "uuid": None,
47 "app_version": 10100,
48 "uptime": (
49 "up time: 16 days, 21:49:56, idle time: 5 days, 20:55:04, "
50 "sleep time: 10 days, 20:46:27"
51 ),
52 "build_fingerprint": (
53 "Fairphone/FP2/FP2:6.0.1/FP2-gms-18.03.1/FP2-gms-18.03.1:user/"
54 "release-keys"
55 ),
56 "radio_version": "4437.1-FP2-0-08",
57 "date": "2018-03-19T09:58:30.386Z",
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040058 }
59
60 DEFAULT_DUMMY_CRASHREPORTS_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020061 DEFAULT_DUMMY_CRASHREPORTS_VALUES.update(
62 {
63 "is_fake_report": 0,
64 "boot_reason": "why?",
65 "power_on_reason": "it was powered on",
66 "power_off_reason": "something happened and it went off",
67 }
68 )
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040069
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +040070 CRASH_TYPE_TO_BOOT_REASON_MAP = {
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020071 "crash": Crashreport.BOOT_REASON_KEYBOARD_POWER_ON,
72 "smpl": Crashreport.BOOT_REASON_RTC_ALARM,
73 "other": "whatever",
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +040074 }
75
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040076 @staticmethod
77 def _update_copy(original, update):
Mitja Nikolaus35181602018-08-03 13:55:30 +020078 """Merge fields of update into a copy of original."""
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040079 data = original.copy()
80 data.update(update)
81 return data
82
83 @staticmethod
84 def device_register_data(**kwargs):
Mitja Nikolaus35181602018-08-03 13:55:30 +020085 """Return the data required to register a device.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040086
Mitja Nikolaus35181602018-08-03 13:55:30 +020087 Use the values passed as keyword arguments or default to the ones
88 from `Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES`.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040089 """
90 return Dummy._update_copy(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +020091 Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES, kwargs
92 )
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040093
94 @staticmethod
95 def heartbeat_data(**kwargs):
Mitja Nikolaus35181602018-08-03 13:55:30 +020096 """Return the data required to create a heartbeat.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +040097
Mitja Nikolaus35181602018-08-03 13:55:30 +020098 Use the values passed as keyword arguments or default to the ones
99 from `Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES`.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400100 """
101 return Dummy._update_copy(Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs)
102
103 @staticmethod
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +0400104 def crashreport_data(report_type=None, **kwargs):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200105 """Return the data required to create a crashreport.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400106
Mitja Nikolaus35181602018-08-03 13:55:30 +0200107 Use the values passed as keyword arguments or default to the ones
108 from `Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES`.
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +0400109
Mitja Nikolaus35181602018-08-03 13:55:30 +0200110 Args:
111 report_type (str, optional): A valid value from
112 `Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.keys()` that will
113 define the boot reason if not explicitly defined in the
114 keyword arguments already.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400115 """
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +0400116 data = Dummy._update_copy(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200117 Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES, kwargs
118 )
119 if report_type and "boot_reason" not in kwargs:
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +0400120 if report_type not in Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP:
121 raise InvalidCrashTypeError(report_type)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200122 data["boot_reason"] = Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.get(
123 report_type
124 )
Borjan Tchakalofffe5f25c2018-03-19 18:33:24 +0400125 return data
Dirk Vogtf2a33422016-10-11 17:17:26 +0200126
127
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400128class DeviceRegisterAPITestCase(APITestCase):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200129 """Base class that offers a device registration method."""
Dirk Vogtf2a33422016-10-11 17:17:26 +0200130
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100131 REGISTER_DEVICE_URL = "api_v1_register_device"
132
Dirk Vogtf2a33422016-10-11 17:17:26 +0200133 def setUp(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200134 """Create an admin user for accessing the API.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400135
Mitja Nikolaus35181602018-08-03 13:55:30 +0200136 The APIClient that can be used to make authenticated requests to the
137 server is stored in self.admin.
138 """
139 admin_user = User.objects.create_superuser(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200140 "somebody", "somebody@example.com", "thepassword"
141 )
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400142 self.admin = APIClient()
Mitja Nikolaus35181602018-08-03 13:55:30 +0200143 self.admin.force_authenticate(admin_user)
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400144
145 def _register_device(self, **kwargs):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200146 """Register a new device.
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400147
148 Arguments:
149 **kwargs: The data to pass the dummy data creation
150 method `Dummy.device_register_data`.
151 Returns:
Mitja Nikolaus35181602018-08-03 13:55:30 +0200152 (UUID, APIClient, str): The uuid of the new device as well as an
153 authentication token and the associated user with credentials.
154
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400155 """
156 data = Dummy.device_register_data(**kwargs)
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100157 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400158 self.assertEqual(response.status_code, status.HTTP_200_OK)
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400159
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200160 uuid = response.data["uuid"]
161 token = response.data["token"]
Mitja Nikolaus35181602018-08-03 13:55:30 +0200162 user = APIClient()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200163 user.credentials(HTTP_AUTHORIZATION="Token " + token)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200164
165 return uuid, user, token
166
167
168class DeviceTestCase(DeviceRegisterAPITestCase):
169 """Test cases for registering devices."""
170
171 def test_register(self):
172 """Test registration of devices."""
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200173 response = self.client.post(
174 reverse(self.REGISTER_DEVICE_URL), Dummy.device_register_data()
175 )
Mitja Nikolaus35181602018-08-03 13:55:30 +0200176 self.assertTrue("token" in response.data)
177 self.assertTrue("uuid" in response.data)
178 self.assertEqual(response.status_code, status.HTTP_200_OK)
179
180 def test_create_missing_fields(self):
181 """Test registration with missing fields."""
182 response = self.client.post(reverse(self.REGISTER_DEVICE_URL))
183 self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
184
185 def test_create_missing_board_date(self):
186 """Test registration with missing board date."""
187 data = Dummy.device_register_data()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200188 data.pop("board_date")
Mitja Nikolaus35181602018-08-03 13:55:30 +0200189 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
190 self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
191
192 def test_create_missing_chipset(self):
193 """Test registration with missing chipset."""
194 data = Dummy.device_register_data()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200195 data.pop("chipset")
Mitja Nikolaus35181602018-08-03 13:55:30 +0200196 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
197 self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
198
199 def test_create_invalid_board_date(self):
200 """Test registration with invalid board date."""
201 data = Dummy.device_register_data()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200202 data["board_date"] = "not_a_valid_date"
Mitja Nikolaus35181602018-08-03 13:55:30 +0200203 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
204 self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
205
206 def test_create_non_existent_time_board_date(self):
207 """Test registration with non existing time.
208
209 Test the resolution of a naive date-time in which the
210 Europe/Amsterdam daylight saving time transition moved the time
211 "forward". The server should not crash when receiving a naive
212 date-time which does not exist in the server timezone or locale.
213 """
214 data = Dummy.device_register_data()
215 # In 2017, the Netherlands changed from CET to CEST on March,
216 # 26 at 02:00
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200217 data["board_date"] = "2017-03-26 02:34:56"
Mitja Nikolaus35181602018-08-03 13:55:30 +0200218 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
219 self.assertEqual(response.status_code, status.HTTP_200_OK)
220
221 def test_create_ambiguous_time_board_date(self):
222 """Test registration with ambiguous time.
223
224 Test the resolution of a naive date-time in which the
225 Europe/Amsterdam daylight saving time transition moved the time
226 "backward". The server should not crash when receiving a naive
227 date-time that can belong to multiple timezones.
228 """
229 data = Dummy.device_register_data()
230 # In 2017, the Netherlands changed from CEST to CET on October,
231 # 29 at 03:00
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200232 data["board_date"] = "2017-10-29 02:34:56"
Mitja Nikolaus35181602018-08-03 13:55:30 +0200233 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
234 self.assertEqual(response.status_code, status.HTTP_200_OK)
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400235
236
237class ListDevicesTestCase(DeviceRegisterAPITestCase):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200238 """Test cases for listing and deleting devices."""
Dirk Vogtf2a33422016-10-11 17:17:26 +0200239
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100240 LIST_CREATE_URL = "api_v1_list_devices"
241 RETRIEVE_URL = "api_v1_retrieve_device"
242
Dirk Vogtf2a33422016-10-11 17:17:26 +0200243 def test_device_list(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200244 """Test registration of 2 devices."""
245 number_of_devices = 2
246 uuids = [
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200247 str(self._register_device()[0]) for _ in range(number_of_devices)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200248 ]
249
250 response = self.admin.get(reverse(self.LIST_CREATE_URL), {})
251 self.assertEqual(response.status_code, status.HTTP_200_OK)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200252 self.assertEqual(len(response.data["results"]), number_of_devices)
253 for result in response.data["results"]:
254 self.assertIn(result["uuid"], uuids)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200255
256 def test_device_list_unauth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200257 """Test listing devices without authentication."""
258 response = self.client.get(reverse(self.LIST_CREATE_URL), {})
259 self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200260
261 def test_retrieve_device_auth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200262 """Test retrieval of devices as admin user."""
263 uuid, _, token = self._register_device()
264 response = self.admin.get(reverse(self.RETRIEVE_URL, args=[uuid]), {})
265 self.assertEqual(response.status_code, status.HTTP_200_OK)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200266 self.assertEqual(response.data["uuid"], str(uuid))
267 self.assertEqual(response.data["token"], token)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200268
269 def test_retrieve_device_unauth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200270 """Test retrieval of devices without authentication."""
271 uuid, _, _ = self._register_device()
272 response = self.client.get(reverse(self.RETRIEVE_URL, args=[uuid]), {})
273 self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200274
275 def test_delete_device_auth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200276 """Test deletion of devices as admin user."""
277 uuid, _, _ = self._register_device()
278 url = reverse(self.RETRIEVE_URL, args=[uuid])
279 response = self.admin.delete(url, {})
280 self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
281 response = self.admin.delete(url, {})
282 self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200283
Dirk Vogtf2a33422016-10-11 17:17:26 +0200284
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400285class HeartbeatListTestCase(DeviceRegisterAPITestCase):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200286 """Test cases for heartbeats."""
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400287
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100288 LIST_CREATE_URL = "api_v1_heartbeats"
289 RETRIEVE_URL = "api_v1_heartbeat"
290 LIST_CREATE_BY_UUID_URL = "api_v1_heartbeats_by_uuid"
291 RETRIEVE_BY_UUID_URL = "api_v1_heartbeat_by_uuid"
292
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400293 @staticmethod
294 def _create_dummy_data(**kwargs):
295 return Dummy.heartbeat_data(**kwargs)
296
297 def _post_multiple(self, client, data, count):
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100298 return [
299 client.post(reverse(self.LIST_CREATE_URL), data)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200300 for _ in range(count)
301 ]
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400302
303 def _retrieve_single(self, user):
304 count = 5
Mitja Nikolaus35181602018-08-03 13:55:30 +0200305 response = self._post_multiple(self.admin, self.data, count)
306 self.assertEqual(len(response), count)
307 self.assertEqual(response[0].status_code, status.HTTP_201_CREATED)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200308 url = reverse(self.RETRIEVE_URL, args=[response[0].data["id"]])
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400309 request = user.get(url)
310 return request.status_code
311
312 def _retrieve_single_by_device(self, user):
313 count = 5
Mitja Nikolaus35181602018-08-03 13:55:30 +0200314 response = self._post_multiple(self.user, self.data, count)
315 self.assertEqual(len(response), count)
316 self.assertEqual(response[0].status_code, status.HTTP_201_CREATED)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200317 url = reverse(
318 self.RETRIEVE_BY_UUID_URL,
319 args=[self.uuid, response[0].data["device_local_id"]],
320 )
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400321 request = user.get(url)
322 return request.status_code
Dirk Vogtf2a33422016-10-11 17:17:26 +0200323
324 def setUp(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200325 """Set up a device and some data."""
326 super().setUp()
327 self.uuid, self.user, self.token = self._register_device()
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400328 self.data = self._create_dummy_data(uuid=self.uuid)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200329
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200330 def test_create_no_auth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200331 """Test creation without authentication."""
332 noauth_client = APIClient()
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200333 response = noauth_client.post(reverse(self.LIST_CREATE_URL), self.data)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200334 self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200335
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200336 def test_create_as_admin(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200337 """Test creation as admin."""
338 response = self.admin.post(reverse(self.LIST_CREATE_URL), self.data)
339 self.assertEqual(response.status_code, status.HTTP_201_CREATED)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200340 self.assertTrue(response.data["id"] > 0)
Dirk Vogtf2a33422016-10-11 17:17:26 +0200341
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200342 def test_create_as_admin_not_existing_device(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200343 """Test creation of heartbeat on non-existing device."""
344 response = self.admin.post(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200345 reverse(self.LIST_CREATE_URL), self._create_dummy_data()
346 )
Mitja Nikolaus35181602018-08-03 13:55:30 +0200347 self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200348
349 def test_create_as_uuid_owner(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200350 """Test creation as owner."""
351 response = self.user.post(
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100352 reverse(self.LIST_CREATE_URL),
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200353 self._create_dummy_data(uuid=self.uuid),
354 )
Mitja Nikolaus35181602018-08-03 13:55:30 +0200355 self.assertEqual(response.status_code, status.HTTP_201_CREATED)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200356 self.assertEqual(response.data["id"], -1)
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200357
358 def test_create_as_uuid_not_owner(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200359 """Test creation as non-owner."""
360 uuid, _, _ = self._register_device()
361 response = self.user.post(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200362 reverse(self.LIST_CREATE_URL), self._create_dummy_data(uuid=uuid)
363 )
Mitja Nikolaus35181602018-08-03 13:55:30 +0200364 self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200365
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200366 def test_list(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200367 """Test listing of heartbeats."""
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200368 count = 5
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400369 self._post_multiple(self.user, self.data, count)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200370 response = self.admin.get(reverse(self.LIST_CREATE_URL))
371 self.assertEqual(response.status_code, status.HTTP_200_OK)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200372 self.assertEqual(len(response.data["results"]), count)
Franz-Xaver Geigerd612fd22018-02-28 10:33:08 +0100373
374 def test_retrieve_single_admin(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200375 """Test retrieval as admin."""
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200376 self.assertEqual(self._retrieve_single(self.admin), status.HTTP_200_OK)
Dirk Vogte1784882016-10-13 16:09:38 +0200377
378 def test_retrieve_single_device_owner(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200379 """Test retrieval as device owner."""
Franz-Xaver Geigerd612fd22018-02-28 10:33:08 +0100380 self.assertEqual(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200381 self._retrieve_single(self.user), status.HTTP_403_FORBIDDEN
382 )
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400383
384 def test_retrieve_single_noauth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200385 """Test retrieval without authentication."""
386 noauth_client = APIClient()
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400387 self.assertEqual(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200388 self._retrieve_single(noauth_client), status.HTTP_401_UNAUTHORIZED
389 )
Dirk Vogte1784882016-10-13 16:09:38 +0200390
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400391 def test_retrieve_single_by_device_admin(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200392 """Test retrieval by device as admin."""
Franz-Xaver Geigerd612fd22018-02-28 10:33:08 +0100393 self.assertEqual(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200394 self._retrieve_single_by_device(self.admin), status.HTTP_200_OK
395 )
Dirk Vogt0d9d5d22016-10-13 16:17:57 +0200396
397 def test_retrieve_single_by_device_device_owner(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200398 """Test retrieval by device as owner."""
Franz-Xaver Geigerd612fd22018-02-28 10:33:08 +0100399 self.assertEqual(
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400400 self._retrieve_single_by_device(self.user),
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200401 status.HTTP_403_FORBIDDEN,
402 )
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400403
404 def test_retrieve_single_by_device_noauth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200405 """Test retrieval by device without authentication."""
406 noauth_client = APIClient()
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400407 self.assertEqual(
Mitja Nikolaus35181602018-08-03 13:55:30 +0200408 self._retrieve_single_by_device(noauth_client),
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200409 status.HTTP_401_UNAUTHORIZED,
410 )
Dirk Vogt0d9d5d22016-10-13 16:17:57 +0200411
Dirk Vogte1784882016-10-13 16:09:38 +0200412 def test_list_by_uuid(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200413 """Test listing of devices by UUID."""
Dirk Vogte1784882016-10-13 16:09:38 +0200414 count = 5
Mitja Nikolaus35181602018-08-03 13:55:30 +0200415 uuid, _, _ = self._register_device()
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400416 self._post_multiple(self.user, self.data, count)
417 self._post_multiple(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200418 self.admin, self._create_dummy_data(uuid=uuid), count
419 )
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100420 url = reverse(self.LIST_CREATE_BY_UUID_URL, args=[self.uuid])
Mitja Nikolaus35181602018-08-03 13:55:30 +0200421 response = self.admin.get(url)
422 self.assertEqual(response.status_code, status.HTTP_200_OK)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200423 self.assertEqual(len(response.data["results"]), count)
Dirk Vogte1784882016-10-13 16:09:38 +0200424
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200425 def test_list_noauth(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200426 """Test listing of devices without authentication."""
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200427 count = 5
Mitja Nikolaus35181602018-08-03 13:55:30 +0200428 noauth_client = APIClient()
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400429 self._post_multiple(self.user, self.data, count)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200430 response = noauth_client.get(reverse(self.LIST_CREATE_URL))
431 self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200432
433 def test_list_device_owner(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200434 """Test listing as device owner."""
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200435 count = 5
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400436 self._post_multiple(self.user, self.data, count)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200437 response = self.user.get(reverse(self.LIST_CREATE_URL))
438 self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200439
Borjan Tchakaloff869cf922018-02-19 11:01:16 +0100440 def test_no_radio_version(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200441 """Test creation and retrieval without radio version."""
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400442 data = self._create_dummy_data(uuid=self.uuid)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200443 data.pop("radio_version")
Mitja Nikolaus35181602018-08-03 13:55:30 +0200444 response = self.user.post(reverse(self.LIST_CREATE_URL), data)
445 self.assertEqual(response.status_code, status.HTTP_201_CREATED)
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100446 url = reverse(self.LIST_CREATE_BY_UUID_URL, args=[self.uuid])
Mitja Nikolaus35181602018-08-03 13:55:30 +0200447 response = self.admin.get(url)
448 self.assertEqual(response.status_code, status.HTTP_200_OK)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200449 self.assertEqual(len(response.data["results"]), 1)
450 self.assertIsNone(response.data["results"][0]["radio_version"])
Borjan Tchakaloff869cf922018-02-19 11:01:16 +0100451
452 def test_radio_version_field(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200453 """Test retrieval of radio version field."""
454 response = self.user.post(reverse(self.LIST_CREATE_URL), self.data)
455 self.assertEqual(response.status_code, status.HTTP_201_CREATED)
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100456 url = reverse(self.LIST_CREATE_BY_UUID_URL, args=[self.uuid])
Mitja Nikolaus35181602018-08-03 13:55:30 +0200457 response = self.admin.get(url)
458 self.assertEqual(response.status_code, status.HTTP_200_OK)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200459 self.assertEqual(len(response.data["results"]), 1)
460 self.assertEqual(
461 response.data["results"][0]["radio_version"],
462 self.data["radio_version"],
463 )
Borjan Tchakaloff869cf922018-02-19 11:01:16 +0100464
Borjan Tchakaloff866f75e2018-03-01 12:04:00 +0400465 def test_send_non_existent_time(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200466 """Test sending of heartbeat with non existent time.
467
468 Test the resolution of a naive date-time in which the
469 Europe/Amsterdam daylight saving time transition moved the time
470 "forward".
Borjan Tchakaloff866f75e2018-03-01 12:04:00 +0400471 """
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400472 data = self._create_dummy_data(uuid=self.uuid)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200473 # In 2017, the Netherlands changed from CET to CEST on March,
474 # 26 at 02:00
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200475 data["date"] = "2017-03-26 02:34:56"
Mitja Nikolaus35181602018-08-03 13:55:30 +0200476 response = self.user.post(reverse(self.LIST_CREATE_URL), data)
477 self.assertEqual(response.status_code, status.HTTP_201_CREATED)
Borjan Tchakaloff866f75e2018-03-01 12:04:00 +0400478
479 def test_send_ambiguous_time(self):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200480 """Test sending of heartbeat with ambiguous time.
481
482 Test the resolution of a naive date-time in which the
483 Europe/Amsterdam daylight saving time transition moved the time
484 "backward".
Borjan Tchakaloff866f75e2018-03-01 12:04:00 +0400485 """
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400486 data = self._create_dummy_data(uuid=self.uuid)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200487 # In 2017, the Netherlands changed from CEST to CET on October,
488 # 29 at 03:00
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200489 data["date"] = "2017-10-29 02:34:56"
Mitja Nikolaus35181602018-08-03 13:55:30 +0200490 response = self.user.post(reverse(self.LIST_CREATE_URL), data)
491 self.assertEqual(response.status_code, status.HTTP_201_CREATED)
Borjan Tchakaloff866f75e2018-03-01 12:04:00 +0400492
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200493
Mitja Nikolaus35181602018-08-03 13:55:30 +0200494# pylint: disable=too-many-ancestors
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200495class CrashreportListTestCase(HeartbeatListTestCase):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200496 """Test cases for crash reports."""
Dirk Vogtc9e10ab2016-10-12 13:58:15 +0200497
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100498 LIST_CREATE_URL = "api_v1_crashreports"
499 RETRIEVE_URL = "api_v1_crashreport"
500 LIST_CREATE_BY_UUID_URL = "api_v1_crashreports_by_uuid"
501 RETRIEVE_BY_UUID_URL = "api_v1_crashreport_by_uuid"
Dirk Vogt67eb1482016-10-13 12:42:56 +0200502
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400503 @staticmethod
504 def _create_dummy_data(**kwargs):
505 return Dummy.crashreport_data(**kwargs)
Dirk Vogt67eb1482016-10-13 12:42:56 +0200506
507
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400508class LogfileUploadTest(DeviceRegisterAPITestCase):
Mitja Nikolaus35181602018-08-03 13:55:30 +0200509 """Test cases for upload of log files."""
Borjan Tchakaloffec814a72018-02-28 11:16:53 +0400510
Franz-Xaver Geiger67504d02018-03-19 15:04:48 +0100511 LIST_CREATE_URL = "api_v1_crashreports"
512 PUT_LOGFILE_URL = "api_v1_putlogfile_for_device_id"
513
Mitja Nikolaus35181602018-08-03 13:55:30 +0200514 def _upload_crashreport(self, user, uuid):
515 """
516 Upload dummy crashreport data.
Dirk Vogt67eb1482016-10-13 12:42:56 +0200517
Mitja Nikolaus35181602018-08-03 13:55:30 +0200518 Args:
519 user: The user which should be used for uploading the report
520 uuid: The uuid of the device to which the report should be uploaded
Dirk Vogt36635692016-10-17 12:19:10 +0200521
Mitja Nikolaus35181602018-08-03 13:55:30 +0200522 Returns: The local id of the device for which the report was uploaded.
523
524 """
525 data = Dummy.crashreport_data(uuid=uuid)
526 response = user.post(reverse(self.LIST_CREATE_URL), data)
527 self.assertEqual(status.HTTP_201_CREATED, response.status_code)
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200528 self.assertTrue("device_local_id" in response.data)
529 device_local_id = response.data["device_local_id"]
Mitja Nikolaus35181602018-08-03 13:55:30 +0200530
531 return device_local_id
532
533 def _test_logfile_upload(self, user, uuid):
534 # Upload crashreport
535 device_local_id = self._upload_crashreport(user, uuid)
536
537 # Upload a logfile for the crashreport
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200538 logfile = tempfile.NamedTemporaryFile("w+", suffix=".log", delete=True)
Mitja Nikolaus35181602018-08-03 13:55:30 +0200539 logfile.write(u"blihblahblub")
540 response = user.post(
Mitja Nikolauscb50f2c2018-08-24 13:54:48 +0200541 reverse(
542 self.PUT_LOGFILE_URL,
543 args=[uuid, device_local_id, os.path.basename(logfile.name)],
544 ),
545 {"file": logfile},
546 format="multipart",
547 )
Mitja Nikolaus35181602018-08-03 13:55:30 +0200548 self.assertEqual(status.HTTP_201_CREATED, response.status_code)
549
550 def test_logfile_upload_as_user(self):
551 """Test upload of logfiles as device owner."""
552 uuid, user, _ = self._register_device()
553 self._test_logfile_upload(user, uuid)
554
555 def test_logfile_upload_as_admin(self):
556 """Test upload of logfiles as admin user."""
557 uuid, _, _ = self._register_device()
558 self._test_logfile_upload(self.admin, uuid)