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