blob: bf35b6b8b96f99b95fe2d9e769f3b0b907c9fa3a [file] [log] [blame]
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02001"""Tests for the rest_endpoints module."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02002import operator
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +01003from datetime import datetime, timedelta
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02004import unittest
5
6import pytz
Franz-Xaver Geiger38a66bc2018-10-09 14:52:26 +02007from django.test import override_settings
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02008
9from django.urls import reverse
10from django.utils.http import urlencode
11
12from rest_framework import status
13
14from crashreport_stats.models import RadioVersion
15from crashreport_stats.tests.utils import Dummy, HiccupStatsAPITestCase
16
17from crashreports.models import Crashreport, HeartBeat, LogFile
Franz-Xaver Geiger38a66bc2018-10-09 14:52:26 +020018from crashreports.tests.utils import DEFAULT_DUMMY_LOG_FILE_DIRECTORY
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020019
20# pylint: disable=too-many-public-methods
21
22
23class StatusTestCase(HiccupStatsAPITestCase):
24 """Test the status endpoint."""
25
26 status_url = reverse("hiccup_stats_api_v1_status")
27
28 def _assert_status_response_is(
29 self, response, num_devices, num_crashreports, num_heartbeats
30 ):
31 self.assertEqual(response.status_code, status.HTTP_200_OK)
32 self.assertIn("devices", response.data)
33 self.assertIn("crashreports", response.data)
34 self.assertIn("heartbeats", response.data)
35 self.assertEqual(response.data["devices"], num_devices)
36 self.assertEqual(response.data["crashreports"], num_crashreports)
37 self.assertEqual(response.data["heartbeats"], num_heartbeats)
38
39 def test_status_url_as_admin(self):
40 """Test that admin users can access the status URL."""
41 self._assert_get_as_admin_user_succeeds(self.status_url)
42
43 def test_status_url_as_fp_staff(self):
44 """Test that Fairphone staff users can access the status URL."""
45 self._assert_get_as_fp_staff_succeeds(self.status_url)
46
47 def test_status_url_as_device_owner(self):
48 """Test that device owner users can not access the status URL."""
49 self._assert_get_as_device_owner_fails(self.status_url)
50
51 def test_status_url_no_auth(self):
52 """Test that non-authenticated users can not access the status URL."""
53 self._assert_get_without_authentication_fails(self.status_url)
54
55 def test_get_status_empty_database(self):
56 """Get the status when the database is empty."""
57 response = self.fp_staff_client.get(self.status_url)
58
59 # Assert that only the device that was created by the setUpTestData()
60 # method is found.
61 self._assert_status_response_is(response, 1, 0, 0)
62
63 def test_get_status(self):
64 """Get the status after some reports have been created."""
65 # Create a device with a heartbeat and a crash report
66 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
67 Dummy.create_dummy_report(HeartBeat, device)
68 Dummy.create_dummy_report(Crashreport, device)
69
70 # Create a second device without any reports
71 Dummy.create_dummy_device(
72 Dummy.create_dummy_user(username=Dummy.USERNAMES[1])
73 )
74
75 # Assert that the status includes the appropriate numbers (a third
76 # device was created by the setUpTestData() method)
77 response = self.fp_staff_client.get(self.status_url)
78 self._assert_status_response_is(
79 response, num_devices=3, num_crashreports=1, num_heartbeats=1
80 )
81
82
83class _VersionTestCase(HiccupStatsAPITestCase):
84 """Abstract class for version-related test cases to inherit from."""
85
86 @staticmethod
87 def _create_dummy_version(**kwargs):
88 return Dummy.create_dummy_version(**kwargs)
89
90 def _get_with_params(self, url, params):
91 return self.admin.get("{}?{}".format(url, urlencode(params)))
92
93 def _assert_result_length_is(self, response, count):
94 self.assertEqual(response.status_code, status.HTTP_200_OK)
95 self.assertIn("results", response.data)
96 self.assertIn("count", response.data)
97 self.assertEqual(response.data["count"], count)
98 self.assertEqual(len(response.data["results"]), count)
99
100 def _assert_filter_result_matches(
101 self, endpoint_url, unique_entry_name, filter_params, expected_result
102 ):
103 # List entities with filter
104 response = self._get_with_params(endpoint_url, filter_params)
105
106 # Expect only the single matching result to be returned
107 self._assert_result_length_is(response, 1)
108 self.assertEqual(
109 response.data["results"][0][unique_entry_name],
110 getattr(expected_result, unique_entry_name),
111 )
112
113
114class VersionTestCase(_VersionTestCase):
115 """Test the Version and REST endpoint."""
116
117 # pylint: disable=too-many-ancestors
118
119 # The attribute name characterising the unicity of a stats entry (the
120 # named identifier)
121 unique_entry_name = "build_fingerprint"
122 # The collection of unique entries to post
123 unique_entries = Dummy.BUILD_FINGERPRINTS
124 # The URL to retrieve the stats entries from
125 endpoint_url = reverse("hiccup_stats_api_v1_versions")
126
127 def _create_version_entities(self):
128 versions = [
129 self._create_dummy_version(**{self.unique_entry_name: unique_entry})
130 for unique_entry in self.unique_entries
131 ]
132 return versions
133
134 def test_endpoint_url_as_admin(self):
135 """Test that admin users can access the endpoint URL."""
136 self._assert_get_as_admin_user_succeeds(self.endpoint_url)
137
138 def test_endpoint_url_as_fp_staff(self):
139 """Test that Fairphone staff users can access the endpoint URL."""
140 self._assert_get_as_fp_staff_succeeds(self.endpoint_url)
141
142 def test_endpoint_url_as_device_owner(self):
143 """Test that device owner users can not access the endpoint URL."""
144 self._assert_get_as_device_owner_fails(self.endpoint_url)
145
146 def test_endpoint_url_no_auth(self):
147 """Test that non-authenticated users can not access the endpoint URL."""
148 self._assert_get_without_authentication_fails(self.endpoint_url)
149
150 def test_list_versions_empty_database(self):
151 """Test listing of versions on an empty database."""
152 response = self.admin.get(self.endpoint_url)
153 self._assert_result_length_is(response, 0)
154
155 def test_list_versions(self):
156 """Test listing versions."""
157 versions = self._create_version_entities()
158 response = self.admin.get(self.endpoint_url)
159 self._assert_result_length_is(response, len(versions))
160
161 def test_filter_versions_by_unique_entry_name(self):
162 """Test filtering versions by their unique entry name."""
163 versions = self._create_version_entities()
164 response = self.admin.get(self.endpoint_url)
165
166 # Listing all entities should return the correct result length
167 self._assert_result_length_is(response, len(versions))
168
169 # List entities with filter
170 filter_params = {
171 self.unique_entry_name: getattr(versions[0], self.unique_entry_name)
172 }
173 self._assert_filter_result_matches(
174 self.endpoint_url,
175 self.unique_entry_name,
176 filter_params,
177 expected_result=versions[0],
178 )
179
180 def test_filter_versions_by_release_type(self):
181 """Test filtering versions by release type."""
182 # Create versions for all combinations of release types
183 versions = []
184 i = 0
185 for is_official_release in True, False:
186 for is_beta_release in True, False:
187 versions.append(
188 self._create_dummy_version(
189 **{
190 "is_official_release": is_official_release,
191 "is_beta_release": is_beta_release,
192 self.unique_entry_name: self.unique_entries[i],
193 }
194 )
195 )
196 i += 1
197
198 # # Listing all entities should return the correct result length
199 response = self.admin.get(self.endpoint_url)
200 self._assert_result_length_is(response, len(versions))
201
202 # List each of the entities with the matching filter params
203 for version in versions:
204 filter_params = {
205 "is_official_release": version.is_official_release,
206 "is_beta_release": version.is_beta_release,
207 }
208 self._assert_filter_result_matches(
209 self.endpoint_url,
210 self.unique_entry_name,
211 filter_params,
212 expected_result=version,
213 )
214
215 def test_filter_versions_by_first_seen_date(self):
216 """Test filtering versions by first seen date."""
217 versions = self._create_version_entities()
218
219 # Set the first seen date of an entity
220 versions[0].first_seen_on = Dummy.DATES[2]
221 versions[0].save()
222
223 # Listing all entities should return the correct result length
224 response = self.admin.get(self.endpoint_url)
225 self._assert_result_length_is(response, len(versions))
226
227 # Expect the single matching result to be returned
228 filter_params = {"first_seen_after": Dummy.DATES[2]}
229 self._assert_filter_result_matches(
230 self.endpoint_url,
231 self.unique_entry_name,
232 filter_params,
233 expected_result=versions[0],
234 )
235
236
237# pylint: disable=too-many-ancestors
238class RadioVersionTestCase(VersionTestCase):
239 """Test the RadioVersion REST endpoint."""
240
241 unique_entry_name = "radio_version"
242 unique_entries = Dummy.RADIO_VERSIONS
243 endpoint_url = reverse("hiccup_stats_api_v1_radio_versions")
244
245 @staticmethod
246 def _create_dummy_version(**kwargs):
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200247 return Dummy.create_dummy_version(RadioVersion, **kwargs)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200248
249
250class VersionDailyTestCase(_VersionTestCase):
251 """Test the VersionDaily REST endpoint."""
252
253 unique_entry_name = "build_fingerprint"
254 unique_entries = Dummy.BUILD_FINGERPRINTS
255 endpoint_url = reverse("hiccup_stats_api_v1_version_daily")
256
257 @staticmethod
258 def _create_dummy_daily_version(version, **kwargs):
259 return Dummy.create_dummy_daily_version(version, **kwargs)
260
261 def _create_version_entities(self):
262 versions = [
263 self._create_dummy_version(**{self.unique_entry_name: unique_entry})
264 for unique_entry in self.unique_entries
265 ]
266 versions_daily = [
267 self._create_dummy_daily_version(version=version)
268 for version in versions
269 ]
270 return versions_daily
271
272 def test_endpoint_url_as_admin(self):
273 """Test that admin users can access the endpoint URL."""
274 self._assert_get_as_admin_user_succeeds(self.endpoint_url)
275
276 def test_endpoint_url_as_fp_staff(self):
277 """Test that Fairphone staff users can access the endpoint URL."""
278 self._assert_get_as_fp_staff_succeeds(self.endpoint_url)
279
280 def test_endpoint_url_as_device_owner(self):
281 """Test that device owner users can not access the endpoint URL."""
282 self._assert_get_as_device_owner_fails(self.endpoint_url)
283
284 def test_endpoint_url_no_auth(self):
285 """Test that non-authenticated users can not access the endpoint URL."""
286 self._assert_get_without_authentication_fails(self.endpoint_url)
287
288 def test_list_daily_versions_empty_database(self):
289 """Test listing of daily versions on an empty database."""
290 response = self.admin.get(self.endpoint_url)
291 self._assert_result_length_is(response, 0)
292
293 def test_list_daily_versions(self):
294 """Test listing daily versions."""
295 versions_daily = self._create_version_entities()
296 response = self.admin.get(self.endpoint_url)
297 self._assert_result_length_is(response, len(versions_daily))
298
299 def test_filter_daily_versions_by_version(self):
300 """Test filtering versions by the version they relate to."""
301 # Create VersionDaily entities
302 versions = self._create_version_entities()
303
304 # Listing all entities should return the correct result length
305 response = self.admin.get(self.endpoint_url)
306 self._assert_result_length_is(response, len(versions))
307
308 # List entities with filter
309 param_name = "version__" + self.unique_entry_name
310 filter_params = {
311 param_name: getattr(versions[0].version, self.unique_entry_name)
312 }
313 self._assert_filter_result_matches(
314 self.endpoint_url,
315 self.unique_entry_name,
316 filter_params,
317 expected_result=versions[0].version,
318 )
319
320 def test_filter_daily_versions_by_date(self):
321 """Test filtering daily versions by date."""
322 # Create Version and VersionDaily entities
323 versions = self._create_version_entities()
324
325 # Update the date
326 versions[0].date = Dummy.DATES[2]
327 versions[0].save()
328
329 # Listing all entities should return the correct result length
330 response = self.admin.get(self.endpoint_url)
331 self._assert_result_length_is(response, len(versions))
332
333 # Expect the single matching result to be returned
334 filter_params = {"date": versions[0].date}
335 self._assert_filter_result_matches(
336 self.endpoint_url,
337 self.unique_entry_name,
338 filter_params,
339 expected_result=versions[0].version,
340 )
341
342
343class RadioVersionDailyTestCase(VersionDailyTestCase):
344 """Test the RadioVersionDaily REST endpoint."""
345
346 unique_entry_name = "radio_version"
347 unique_entries = Dummy.RADIO_VERSIONS
348 endpoint_url = reverse("hiccup_stats_api_v1_radio_version_daily")
349
350 @staticmethod
351 def _create_dummy_version(**kwargs):
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200352 return Dummy.create_dummy_version(RadioVersion, **kwargs)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200353
354 @staticmethod
355 def _create_dummy_daily_version(version, **kwargs):
356 return Dummy.create_dummy_daily_radio_version(version, **kwargs)
357
358
Franz-Xaver Geiger38a66bc2018-10-09 14:52:26 +0200359@override_settings(MEDIA_ROOT=DEFAULT_DUMMY_LOG_FILE_DIRECTORY)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200360class DeviceStatsTestCase(HiccupStatsAPITestCase):
361 """Test the single device stats REST endpoints."""
362
363 device_overview_url = "hiccup_stats_api_v1_device_overview"
364 device_report_history_url = "hiccup_stats_api_v1_device_report_history"
365 device_update_history_url = "hiccup_stats_api_v1_device_update_history"
366 device_logfile_download_url = "hiccup_stats_api_v1_logfile_download"
367
368 def _get_with_params(self, url, params):
369 url = reverse(url, kwargs=params)
370 return self.fp_staff_client.get(url)
371
372 def _assert_device_stats_response_is(
373 self,
374 response,
375 uuid,
376 board_date,
377 num_heartbeats,
378 num_crashreports,
379 num_smpls,
380 crashes_per_day,
381 smpl_per_day,
382 last_active,
383 ):
384 # pylint: disable=too-many-arguments
385 self.assertEqual(response.status_code, status.HTTP_200_OK)
386
387 self.assertIn("uuid", response.data)
388 self.assertIn("board_date", response.data)
389 self.assertIn("heartbeats", response.data)
390 self.assertIn("crashreports", response.data)
391 self.assertIn("smpls", response.data)
392 self.assertIn("crashes_per_day", response.data)
393 self.assertIn("smpl_per_day", response.data)
394 self.assertIn("last_active", response.data)
395
396 self.assertEqual(response.data["uuid"], uuid)
397 self.assertEqual(response.data["board_date"], board_date)
398 self.assertEqual(response.data["heartbeats"], num_heartbeats)
399 self.assertEqual(response.data["crashreports"], num_crashreports)
400 self.assertEqual(response.data["smpls"], num_smpls)
401 self.assertEqual(response.data["crashes_per_day"], crashes_per_day)
402 self.assertEqual(response.data["smpl_per_day"], smpl_per_day)
403 self.assertEqual(response.data["last_active"], last_active)
404
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200405 def test_device_overview_url_as_admin(self):
406 """Test that admin users can access the URL."""
407 self._assert_get_as_admin_user_succeeds(
408 reverse(
409 self.device_overview_url,
410 kwargs={"uuid": self.device_owner_device.uuid},
411 )
412 )
413
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200414 def test_device_overview_url_as_fp_staff(self):
415 """Test that Fairphone staff users can access the URL."""
416 self._assert_get_as_fp_staff_succeeds(
417 reverse(
418 self.device_overview_url,
419 kwargs={"uuid": self.device_owner_device.uuid},
420 )
421 )
422
423 def test_device_overview_url_as_device_owner(self):
424 """Test that device owner users can not access the URL."""
425 self._assert_get_as_device_owner_fails(
426 reverse(
427 self.device_overview_url,
428 kwargs={"uuid": self.device_owner_device.uuid},
429 )
430 )
431
432 def test_device_overview_url_no_auth(self):
433 """Test that non-authenticated users can not access the URL."""
434 self._assert_get_without_authentication_fails(
435 reverse(
436 self.device_overview_url,
437 kwargs={"uuid": self.device_owner_device.uuid},
438 )
439 )
440
441 def test_device_report_history_url_as_admin(self):
442 """Test that admin users can access device report history URL."""
443 self._assert_get_as_admin_user_succeeds(
444 reverse(
445 self.device_report_history_url,
446 kwargs={"uuid": self.device_owner_device.uuid},
447 )
448 )
449
450 def test_device_report_history_url_as_fp_staff(self):
451 """Test that FP staff can access device report history URL."""
452 self._assert_get_as_fp_staff_succeeds(
453 reverse(
454 self.device_report_history_url,
455 kwargs={"uuid": self.device_owner_device.uuid},
456 )
457 )
458
459 def test_device_report_history_url_as_device_owner(self):
460 """Test that device owners can't access device report history URL."""
461 self._assert_get_as_device_owner_fails(
462 reverse(
463 self.device_report_history_url,
464 kwargs={"uuid": self.device_owner_device.uuid},
465 )
466 )
467
468 def test_device_report_history_url_no_auth(self):
469 """Test that device report history is not accessible without auth."""
470 self._assert_get_without_authentication_fails(
471 reverse(
472 self.device_report_history_url,
473 kwargs={"uuid": self.device_owner_device.uuid},
474 )
475 )
476
477 def test_device_update_history_url_as_admin(self):
478 """Test that admin users can access device update history URL."""
479 self._assert_get_as_admin_user_succeeds(
480 reverse(
481 self.device_update_history_url,
482 kwargs={"uuid": self.device_owner_device.uuid},
483 )
484 )
485
486 def test_device_update_history_url_as_fp_staff(self):
487 """Test that FP staff can access device update history URL."""
488 self._assert_get_as_fp_staff_succeeds(
489 reverse(
490 self.device_update_history_url,
491 kwargs={"uuid": self.device_owner_device.uuid},
492 )
493 )
494
495 def test_device_update_history_url_as_device_owner(self):
496 """Test that device owners can't access device update history URL."""
497 self._assert_get_as_device_owner_fails(
498 reverse(
499 self.device_update_history_url,
500 kwargs={"uuid": self.device_owner_device.uuid},
501 )
502 )
503
504 def test_device_update_history_url_no_auth(self):
505 """Test that device update history is not accessible without auth."""
506 self._assert_get_without_authentication_fails(
507 reverse(
508 self.device_update_history_url,
509 kwargs={"uuid": self.device_owner_device.uuid},
510 )
511 )
512
513 def test_logfile_download_url_as_admin(self):
514 """Test that admin users can access the logfile download URL."""
515 non_existent_logfile_id = 0
516 self.assertFalse(
517 LogFile.objects.filter(id=non_existent_logfile_id).exists()
518 )
519 self._assert_get_as_admin_user_succeeds(
520 reverse(
521 self.device_logfile_download_url,
522 kwargs={"id_logfile": non_existent_logfile_id},
523 ),
524 expected_status=status.HTTP_404_NOT_FOUND,
525 )
526
527 def tes_logfile_download_url_as_fp_staff(self):
528 """Test that FP staff can access the logfile download URL."""
529 non_existent_logfile_id = 0
530 self.assertFalse(
531 LogFile.objects.filter(id=non_existent_logfile_id).exists()
532 )
533 self._assert_get_as_fp_staff_succeeds(
534 reverse(
535 self.device_logfile_download_url,
536 kwargs={"id_logfile": non_existent_logfile_id},
537 ),
538 expected_status=status.HTTP_404_NOT_FOUND,
539 )
540
541 def test_logfile_download_url_as_device_owner(self):
542 """Test that device owners can't access the logfile download URL."""
543 non_existent_logfile_id = 0
544 self.assertFalse(
545 LogFile.objects.filter(id=non_existent_logfile_id).exists()
546 )
547 self._assert_get_as_device_owner_fails(
548 reverse(
549 self.device_logfile_download_url,
550 kwargs={"id_logfile": non_existent_logfile_id},
551 )
552 )
553
554 def test_logfile_download_url_no_auth(self):
555 """Test that the logfile download URL is not accessible without auth."""
556 non_existent_logfile_id = 0
557 self.assertFalse(
558 LogFile.objects.filter(id=non_existent_logfile_id).exists()
559 )
560 self._assert_get_without_authentication_fails(
561 reverse(
562 self.device_logfile_download_url,
563 kwargs={"id_logfile": non_existent_logfile_id},
564 )
565 )
566
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200567 def test_get_device_stats_no_reports(self):
568 """Test getting device stats for a device without reports."""
569 # Create a device
570 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
571
572 # Get the device statistics
573 response = self._get_with_params(
574 self.device_overview_url, {"uuid": device.uuid}
575 )
576
577 # Assert that the statistics match
578 self._assert_device_stats_response_is(
579 response=response,
580 uuid=str(device.uuid),
581 board_date=device.board_date,
582 num_heartbeats=0,
583 num_crashreports=0,
584 num_smpls=0,
585 crashes_per_day=0.0,
586 smpl_per_day=0.0,
587 last_active=device.board_date,
588 )
589
590 def test_get_device_stats_no_crash_reports(self):
591 """Test getting device stats for a device without crashreports."""
592 # Create a device and a heartbeat
593 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
594 heartbeat = Dummy.create_dummy_report(HeartBeat, device)
595
596 # Get the device statistics
597 response = self._get_with_params(
598 self.device_overview_url, {"uuid": device.uuid}
599 )
600
601 # Assert that the statistics match
602 self._assert_device_stats_response_is(
603 response=response,
604 uuid=str(device.uuid),
605 board_date=device.board_date,
606 num_heartbeats=1,
607 num_crashreports=0,
608 num_smpls=0,
609 crashes_per_day=0.0,
610 smpl_per_day=0.0,
611 last_active=heartbeat.date,
612 )
613
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200614 def test_get_device_stats_no_heartbeats(self):
615 """Test getting device stats for a device without heartbeats."""
616 # Create a device and crashreport
617 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
618 Dummy.create_dummy_report(Crashreport, device)
619
620 # Get the device statistics
621 response = self._get_with_params(
622 self.device_overview_url, {"uuid": device.uuid}
623 )
624
625 # Assert that the statistics match
626 self._assert_device_stats_response_is(
627 response=response,
628 uuid=str(device.uuid),
629 board_date=device.board_date,
630 num_heartbeats=0,
631 num_crashreports=1,
632 num_smpls=0,
633 crashes_per_day=0.0,
634 smpl_per_day=0.0,
635 last_active=device.board_date,
636 )
637
638 def test_get_device_stats(self):
639 """Test getting device stats for a device."""
640 # Create a device with a heartbeat and one report of each type
641 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
642 heartbeat = Dummy.create_dummy_report(HeartBeat, device)
643 for boot_reason in (
644 Crashreport.SMPL_BOOT_REASONS
645 + Crashreport.CRASH_BOOT_REASONS
646 + ["other boot reason"]
647 ):
648 Dummy.create_dummy_report(
649 Crashreport, device, boot_reason=boot_reason
650 )
651
652 # Get the device statistics
653 response = self._get_with_params(
654 self.device_overview_url, {"uuid": device.uuid}
655 )
656
657 # Assert that the statistics match
658 self._assert_device_stats_response_is(
659 response=response,
660 uuid=str(device.uuid),
661 board_date=device.board_date,
662 num_heartbeats=1,
663 num_crashreports=len(Crashreport.CRASH_BOOT_REASONS),
664 num_smpls=len(Crashreport.SMPL_BOOT_REASONS),
665 crashes_per_day=len(Crashreport.CRASH_BOOT_REASONS),
666 smpl_per_day=len(Crashreport.SMPL_BOOT_REASONS),
667 last_active=heartbeat.date,
668 )
669
670 def test_get_device_stats_multiple_days(self):
671 """Test getting device stats for a device that sent more reports."""
672 # Create a device with some heartbeats and reports over time
673 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
674 num_days = 100
675 for i in range(num_days):
676 report_day = datetime.now(tz=pytz.utc) + timedelta(days=i)
677 heartbeat = Dummy.create_dummy_report(
678 HeartBeat, device, date=report_day
679 )
680 Dummy.create_dummy_report(Crashreport, device, date=report_day)
681 Dummy.create_dummy_report(
682 Crashreport,
683 device,
684 date=report_day,
685 boot_reason=Crashreport.SMPL_BOOT_REASONS[0],
686 )
687
688 # Get the device statistics
689 response = self._get_with_params(
690 self.device_overview_url, {"uuid": device.uuid}
691 )
692
693 # Assert that the statistics match
694 self._assert_device_stats_response_is(
695 response=response,
696 uuid=str(device.uuid),
697 board_date=device.board_date,
698 num_heartbeats=num_days,
699 num_crashreports=num_days,
700 num_smpls=num_days,
701 crashes_per_day=1,
702 smpl_per_day=1,
703 last_active=heartbeat.date,
704 )
705
706 def test_get_device_stats_multiple_days_missing_heartbeat(self):
707 """Test getting device stats for a device with missing heartbeat."""
708 # Create a device with some heartbeats and reports over time
709 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
710 num_days = 100
711 skip_day = round(num_days / 2)
712 for i in range(num_days):
713 report_day = datetime.now(tz=pytz.utc) + timedelta(days=i)
714 # Skip creation of heartbeat at one day
715 if i != skip_day:
716 heartbeat = Dummy.create_dummy_report(
717 HeartBeat, device, date=report_day
718 )
719 Dummy.create_dummy_report(Crashreport, device, date=report_day)
720
721 # Get the device statistics
722 response = self._get_with_params(
723 self.device_overview_url, {"uuid": device.uuid}
724 )
725
726 # Assert that the statistics match
727 self._assert_device_stats_response_is(
728 response=response,
729 uuid=str(device.uuid),
730 board_date=device.board_date,
731 num_heartbeats=num_days - 1,
732 num_crashreports=num_days,
733 num_smpls=0,
734 crashes_per_day=num_days / (num_days - 1),
735 smpl_per_day=0,
736 last_active=heartbeat.date,
737 )
738
739 @unittest.skip("Duplicate heartbeats are currently not dropped.")
740 def test_get_device_stats_multiple_days_duplicate_heartbeat(self):
741 """Test getting device stats for a device with duplicate heartbeat.
742
743 Duplicate heartbeats are dropped and thus should not influence the
744 statistics.
745 """
746 # Create a device with some heartbeats and reports over time
747 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
748 num_days = 100
749 duplicate_day = round(num_days / 2)
750 first_report_day = Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES["date"]
751 for i in range(num_days):
752 report_day = first_report_day + timedelta(days=i)
753 heartbeat = Dummy.create_dummy_report(
754 HeartBeat, device, date=report_day
755 )
756 # Create a second at the duplicate day (with 1 hour delay)
757 if i == duplicate_day:
758 Dummy.create_dummy_report(
759 HeartBeat, device, date=report_day + timedelta(hours=1)
760 )
761 Dummy.create_dummy_report(Crashreport, device, date=report_day)
762
763 # Get the device statistics
764 response = self._get_with_params(
765 self.device_overview_url, {"uuid": device.uuid}
766 )
767
768 # Assert that the statistics match
769 self._assert_device_stats_response_is(
770 response=response,
771 uuid=str(device.uuid),
772 board_date=device.board_date,
773 num_heartbeats=num_days,
774 num_crashreports=num_days,
775 num_smpls=0,
776 crashes_per_day=1,
777 smpl_per_day=0,
778 last_active=heartbeat.date,
779 )
780
781 def test_get_device_report_history_no_reports(self):
782 """Test getting report history stats for a device without reports."""
783 # Create a device
784 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
785
786 # Get the device report history statistics
787 response = self._get_with_params(
788 self.device_report_history_url, {"uuid": device.uuid}
789 )
790
791 # Assert that the report history is empty
792 self.assertEqual([], response.data)
793
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200794 def test_get_device_report_history(self):
795 """Test getting report history stats for a device."""
796 # Create a device with a heartbeat and one report of each type
797 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
798 heartbeat = Dummy.create_dummy_report(HeartBeat, device)
799 for boot_reason in (
800 Crashreport.SMPL_BOOT_REASONS
801 + Crashreport.CRASH_BOOT_REASONS
802 + ["other boot reason"]
803 ):
804 Dummy.create_dummy_report(
805 Crashreport, device, boot_reason=boot_reason
806 )
807
808 # Get the device report history statistics
809 response = self._get_with_params(
810 self.device_report_history_url, {"uuid": device.uuid}
811 )
812
813 # Assert that the statistics match
814 report_history = [
815 {
816 "date": heartbeat.date.date(),
817 "heartbeats": 1,
818 "smpl": len(Crashreport.SMPL_BOOT_REASONS),
819 "prob_crashes": len(Crashreport.CRASH_BOOT_REASONS),
820 "other": 1,
821 }
822 ]
823 self.assertEqual(report_history, response.data)
824
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +0100825 def test_get_device_report_history_multiple_days(self):
826 """Test getting report history stats for a device for multiple days."""
827 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
828 expected_report_history = []
829
830 # Create a device with a heartbeat and one report of each type for 10
831 # days
832 report_date = Dummy.DEFAULT_DUMMY_CRASHREPORT_VALUES["date"]
833 for _ in range(10):
834 report_date = report_date + timedelta(days=1)
835
836 Dummy.create_dummy_report(HeartBeat, device, date=report_date)
837 for boot_reason in (
838 Crashreport.SMPL_BOOT_REASONS
839 + Crashreport.CRASH_BOOT_REASONS
840 + ["other boot reason"]
841 ):
842 Dummy.create_dummy_report(
843 Crashreport,
844 device,
845 boot_reason=boot_reason,
846 date=report_date,
847 )
848
849 # Create the expected report history object
850 expected_report_history.append(
851 {
852 "date": report_date.date(),
853 "heartbeats": 1,
854 "smpl": len(Crashreport.SMPL_BOOT_REASONS),
855 "prob_crashes": len(Crashreport.CRASH_BOOT_REASONS),
856 "other": 1,
857 }
858 )
859
860 # Sort the expected values by date
861 expected_report_history.sort(key=operator.itemgetter("date"))
862
863 # Get the device report history statistics
864 response = self._get_with_params(
865 self.device_report_history_url, {"uuid": device.uuid}
866 )
867
868 # Assert that the statistics match
869 self.assertEqual(expected_report_history, response.data)
870
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200871 def test_get_device_update_history_no_reports(self):
872 """Test getting update history stats for a device without reports."""
873 # Create a device
874 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
875
876 # Get the device report history statistics
877 response = self._get_with_params(
878 self.device_update_history_url, {"uuid": device.uuid}
879 )
880
881 # Assert that the update history is empty
882 self.assertEqual([], response.data)
883
884 def test_get_device_update_history(self):
885 """Test getting update history stats for a device."""
886 # Create a device with a heartbeat and one report of each type
887 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
888 heartbeat = Dummy.create_dummy_report(HeartBeat, device)
889 for boot_reason in (
890 Crashreport.SMPL_BOOT_REASONS
891 + Crashreport.CRASH_BOOT_REASONS
892 + ["other boot reason"]
893 ):
894 params = {"boot_reason": boot_reason}
895 Dummy.create_dummy_report(Crashreport, device, **params)
896
897 # Get the device update history statistics
898 response = self._get_with_params(
899 self.device_update_history_url, {"uuid": device.uuid}
900 )
901
902 # Assert that the statistics match
903 update_history = [
904 {
905 "build_fingerprint": heartbeat.build_fingerprint,
906 "heartbeats": 1,
907 "max": device.id,
908 "other": 1,
909 "prob_crashes": len(Crashreport.CRASH_BOOT_REASONS),
910 "smpl": len(Crashreport.SMPL_BOOT_REASONS),
911 "update_date": heartbeat.date,
912 }
913 ]
914 self.assertEqual(update_history, response.data)
915
916 def test_get_device_update_history_multiple_updates(self):
917 """Test getting update history stats with multiple updates."""
918 # Create a device with a heartbeats and crashreport for each build
919 # fingerprint in the dummy values
920 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
921 expected_update_history = []
922 for i, build_fingerprint in enumerate(Dummy.BUILD_FINGERPRINTS):
923 report_day = datetime.now(tz=pytz.utc) + timedelta(days=i)
924 Dummy.create_dummy_report(
925 HeartBeat,
926 device,
927 date=report_day,
928 build_fingerprint=build_fingerprint,
929 )
930 Dummy.create_dummy_report(
931 Crashreport,
932 device,
933 date=report_day,
934 build_fingerprint=build_fingerprint,
935 )
936
937 # Create the expected update history object
938 expected_update_history.append(
939 {
940 "update_date": report_day,
941 "build_fingerprint": build_fingerprint,
942 "max": device.id,
943 "prob_crashes": 1,
944 "smpl": 0,
945 "other": 0,
946 "heartbeats": 1,
947 }
948 )
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +0100949 # Sort the expected values by update date
950 expected_update_history.sort(key=operator.itemgetter("update_date"))
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200951
Mitja Nikolaus21f3b1b2018-11-02 17:10:02 +0100952 # Get the device update history statistics
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200953 response = self._get_with_params(
954 self.device_update_history_url, {"uuid": device.uuid}
955 )
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200956
957 # Assert that the statistics match
958 self.assertEqual(expected_update_history, response.data)
959
960 def test_download_non_existing_logfile(self):
961 """Test download of a non existing log file."""
962 # Try to get a log file
963 response = self._get_with_params(
964 self.device_logfile_download_url, {"id_logfile": 0}
965 )
966
967 # Assert that the log file was not found
968 self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
969
970 def test_download_logfile(self):
971 """Test download of log files."""
972 # Create a device with a crash report along with log file
973 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
974 crashreport = Dummy.create_dummy_report(Crashreport, device)
975 logfile = Dummy.create_dummy_log_file(crashreport)
976
977 # Get the log file
978 response = self._get_with_params(
979 self.device_logfile_download_url, {"id_logfile": logfile.id}
980 )
981
982 # Assert that the log file contents are in the response data
983 self.assertEqual(response.status_code, status.HTTP_200_OK)
984 self.assertIn(Dummy.DEFAULT_DUMMY_LOG_FILE_NAME, response.data)
985 expected_logfile_content = Dummy.read_logfile_contents(
986 logfile.logfile.path, Dummy.DEFAULT_DUMMY_LOG_FILE_NAME
987 )
988 self.assertEqual(
989 response.data[Dummy.DEFAULT_DUMMY_LOG_FILE_NAME],
990 expected_logfile_content,
991 )