blob: 1202fceabd4ce072a5f37b6b004c0ce98a098df1 [file] [log] [blame]
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02001"""Utility functions shared by all crashreports tests."""
2
Mitja Nikolaus6e118472018-10-04 11:15:29 +02003import os
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02004from typing import Optional
5
Mitja Nikolause0e83772018-11-05 10:00:53 +01006from django.contrib.auth.models import User, Group
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02007from django.urls import reverse
8from rest_framework import status
9from rest_framework.test import APITestCase, APIClient
10
11from crashreports.models import Crashreport
Mitja Nikolause0e83772018-11-05 10:00:53 +010012from hiccup.allauth_adapters import FP_STAFF_GROUP_NAME
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020013
Franz-Xaver Geiger38a66bc2018-10-09 14:52:26 +020014DEFAULT_DUMMY_LOG_FILE_DIRECTORY = os.path.join("resources", "test")
15
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020016
17class InvalidCrashTypeError(BaseException):
18 """Invalid crash type encountered.
19
20 The valid crash type values (strings) are:
21 - 'crash';
22 - 'smpl';
23 - 'other'.
24
25 Args:
26 - crash_type: The invalid crash type.
27 """
28
29 def __init__(self, crash_type):
30 """Initialise the exception using the crash type to build a message.
31
32 Args:
33 crash_type: The invalid crash type.
34 """
35 super(InvalidCrashTypeError, self).__init__(
36 "{} is not a valid crash type".format(crash_type)
37 )
38
39
40class Dummy:
41 """Dummy values for devices, heartbeats and crashreports."""
42
43 DEFAULT_DUMMY_DEVICE_REGISTER_VALUES = {
44 "board_date": "2015-12-15T01:23:45Z",
45 "chipset": "Qualcomm MSM8974PRO-AA",
46 }
47
48 DEFAULT_DUMMY_HEARTBEAT_VALUES = {
49 "uuid": None,
50 "app_version": 10100,
51 "uptime": (
52 "up time: 16 days, 21:49:56, idle time: 5 days, 20:55:04, "
53 "sleep time: 10 days, 20:46:27"
54 ),
55 "build_fingerprint": (
56 "Fairphone/FP2/FP2:6.0.1/FP2-gms-18.03.1/FP2-gms-18.03.1:user/"
57 "release-keys"
58 ),
59 "radio_version": "4437.1-FP2-0-08",
60 "date": "2018-03-19T09:58:30.386Z",
61 }
62
63 DEFAULT_DUMMY_CRASHREPORTS_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
64 DEFAULT_DUMMY_CRASHREPORTS_VALUES.update(
65 {
66 "is_fake_report": 0,
67 "boot_reason": "why?",
68 "power_on_reason": "it was powered on",
69 "power_off_reason": "something happened and it went off",
70 }
71 )
72
73 CRASH_TYPE_TO_BOOT_REASON_MAP = {
74 "crash": Crashreport.BOOT_REASON_KEYBOARD_POWER_ON,
75 "smpl": Crashreport.BOOT_REASON_RTC_ALARM,
76 "other": "whatever",
77 }
78
Mitja Nikolaus6e118472018-10-04 11:15:29 +020079 DEFAULT_DUMMY_LOG_FILE_PATH = os.path.join(
Franz-Xaver Geiger38a66bc2018-10-09 14:52:26 +020080 DEFAULT_DUMMY_LOG_FILE_DIRECTORY, "test_logfile.zip"
Mitja Nikolaus6e118472018-10-04 11:15:29 +020081 )
82
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020083 @staticmethod
84 def _update_copy(original, update):
85 """Merge fields of update into a copy of original."""
86 data = original.copy()
87 data.update(update)
88 return data
89
90 @staticmethod
91 def device_register_data(**kwargs):
92 """Return the data required to register a device.
93
94 Use the values passed as keyword arguments or default to the ones
95 from `Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES`.
96 """
97 return Dummy._update_copy(
98 Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES, kwargs
99 )
100
101 @staticmethod
102 def heartbeat_data(**kwargs):
103 """Return the data required to create a heartbeat.
104
105 Use the values passed as keyword arguments or default to the ones
106 from `Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES`.
107 """
108 return Dummy._update_copy(Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs)
109
110 @staticmethod
111 def crashreport_data(report_type: Optional[str] = None, **kwargs):
112 """Return the data required to create a crashreport.
113
114 Use the values passed as keyword arguments or default to the ones
115 from `Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES`.
116
117 Args:
118 report_type: A valid value from
119 `Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.keys()` that will
120 define the boot reason if not explicitly defined in the
121 keyword arguments already.
122 """
123 data = Dummy._update_copy(
124 Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES, kwargs
125 )
126 if report_type and "boot_reason" not in kwargs:
127 if report_type not in Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP:
128 raise InvalidCrashTypeError(report_type)
129 data["boot_reason"] = Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.get(
130 report_type
131 )
132 return data
133
134
135class HiccupCrashreportsAPITestCase(APITestCase):
136 """Base class that offers a device registration method."""
137
138 REGISTER_DEVICE_URL = "api_v1_register_device"
139
140 def setUp(self):
Mitja Nikolause0e83772018-11-05 10:00:53 +0100141 """Create a Fairphone staff user for accessing the API.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200142
143 The APIClient that can be used to make authenticated requests to the
Mitja Nikolause0e83772018-11-05 10:00:53 +0100144 server is stored in self.fp_staff_client.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200145 """
Mitja Nikolause0e83772018-11-05 10:00:53 +0100146 fp_staff_group = Group.objects.get(name=FP_STAFF_GROUP_NAME)
147 fp_staff_user = User.objects.create_user(
148 "fp_staff", "somebody@fairphone.com", "thepassword"
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200149 )
Mitja Nikolause0e83772018-11-05 10:00:53 +0100150 fp_staff_user.groups.add(fp_staff_group)
151 self.fp_staff_client = APIClient()
152 self.fp_staff_client.force_login(fp_staff_user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200153
154 def _register_device(self, **kwargs):
155 """Register a new device.
156
157 Arguments:
158 **kwargs: The data to pass the dummy data creation
159 method `Dummy.device_register_data`.
160 Returns:
161 (UUID, APIClient, str): The uuid of the new device as well as an
162 authentication token and the associated user with credentials.
163
164 """
165 data = Dummy.device_register_data(**kwargs)
166 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
167 self.assertEqual(response.status_code, status.HTTP_200_OK)
168
169 uuid = response.data["uuid"]
170 token = response.data["token"]
171 user = APIClient()
172 user.credentials(HTTP_AUTHORIZATION="Token " + token)
173
174 return uuid, user, token