blob: 9ee316a428712a8f8e65ab72c1a413b41815fff3 [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 Nikolaus7dc86722018-11-27 14:57:39 +01004import zipfile
5from datetime import date, datetime
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02006from typing import Optional
7
Mitja Nikolaus7dc86722018-11-27 14:57:39 +01008import pytz
Mitja Nikolause0e83772018-11-05 10:00:53 +01009from django.contrib.auth.models import User, Group
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020010from django.urls import reverse
11from rest_framework import status
12from rest_framework.test import APITestCase, APIClient
13
Mitja Nikolaus7dc86722018-11-27 14:57:39 +010014from crashreports.models import Crashreport, Device, HeartBeat, LogFile
Mitja Nikolause0e83772018-11-05 10:00:53 +010015from hiccup.allauth_adapters import FP_STAFF_GROUP_NAME
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020016
17
18class InvalidCrashTypeError(BaseException):
19 """Invalid crash type encountered.
20
21 The valid crash type values (strings) are:
22 - 'crash';
23 - 'smpl';
24 - 'other'.
25
26 Args:
27 - crash_type: The invalid crash type.
28 """
29
30 def __init__(self, crash_type):
31 """Initialise the exception using the crash type to build a message.
32
33 Args:
34 crash_type: The invalid crash type.
35 """
36 super(InvalidCrashTypeError, self).__init__(
37 "{} is not a valid crash type".format(crash_type)
38 )
39
40
41class Dummy:
42 """Dummy values for devices, heartbeats and crashreports."""
43
Mitja Nikolaus7dc86722018-11-27 14:57:39 +010044 # Valid unique entries
45 BUILD_FINGERPRINTS = [
46 (
47 "Fairphone/FP2/FP2:5.1/FP2/r4275.1_FP2_gms76_1.13.0"
48 ":user/release-keys"
49 ),
50 (
51 "Fairphone/FP2/FP2:5.1.1/FP2-gms75.1.13.0/FP2-gms75.1.13.0"
52 ":user/release-keys"
53 ),
54 (
55 "Fairphone/FP2/FP2:6.0.1/FP2-gms-18.04.1/FP2-gms-18.04.1"
56 ":user/release-keys"
57 ),
58 ("Fairphone/FP2/FP2:7.1.2/18.07.2/gms-7480c31d:user/release-keys"),
59 ]
60 RADIO_VERSIONS = [
61 "4437.1-FP2-0-07",
62 "4437.1-FP2-0-08",
63 "4437.1-FP2-0-09",
64 "4437.1-FP2-0-10",
65 ]
66 UUIDs = ["e1c0cc95-ab8d-461a-a768-cb8d9d7fdb04"]
67
68 USERNAMES = ["testuser1", "testuser2", "testuser3"]
69
70 DATES = [date(2018, 3, 19), date(2018, 3, 26), date(2018, 5, 1)]
71
72 DEFAULT_DUMMY_USER_VALUES = {"username": USERNAMES[0]}
73
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020074 DEFAULT_DUMMY_DEVICE_REGISTER_VALUES = {
Mitja Nikolaus7dc86722018-11-27 14:57:39 +010075 "board_date": datetime(2015, 12, 15, 1, 23, 45, tzinfo=pytz.utc),
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020076 "chipset": "Qualcomm MSM8974PRO-AA",
77 }
78
Mitja Nikolaus7dc86722018-11-27 14:57:39 +010079 DEFAULT_DUMMY_DEVICE_VALUES = DEFAULT_DUMMY_DEVICE_REGISTER_VALUES.copy()
80 DEFAULT_DUMMY_DEVICE_VALUES.update(
81 {"token": "64111c62d521fb4724454ca6dea27e18f93ef56e"}
82 )
83
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020084 DEFAULT_DUMMY_HEARTBEAT_VALUES = {
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020085 "app_version": 10100,
86 "uptime": (
87 "up time: 16 days, 21:49:56, idle time: 5 days, 20:55:04, "
88 "sleep time: 10 days, 20:46:27"
89 ),
Mitja Nikolaus7dc86722018-11-27 14:57:39 +010090 "build_fingerprint": BUILD_FINGERPRINTS[0],
91 "radio_version": RADIO_VERSIONS[0],
92 "date": datetime(2018, 3, 19, 12, 0, 0, tzinfo=pytz.utc),
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020093 }
94
Mitja Nikolaus7dc86722018-11-27 14:57:39 +010095 DEFAULT_DUMMY_CRASHREPORT_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
96 DEFAULT_DUMMY_CRASHREPORT_VALUES.update(
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020097 {
98 "is_fake_report": 0,
Mitja Nikolaus7dc86722018-11-27 14:57:39 +010099 "boot_reason": Crashreport.BOOT_REASON_UNKOWN,
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200100 "power_on_reason": "it was powered on",
101 "power_off_reason": "something happened and it went off",
102 }
103 )
104
Mitja Nikolaus7dc86722018-11-27 14:57:39 +0100105 DEFAULT_DUMMY_LOG_FILE_VALUES = {
106 "logfile_type": "last_kmsg",
107 "logfile": "test_logfile.zip",
108 }
109
110 DEFAULT_DUMMY_LOG_FILE_NAME = "dmesg.log"
111
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200112 CRASH_TYPE_TO_BOOT_REASON_MAP = {
113 "crash": Crashreport.BOOT_REASON_KEYBOARD_POWER_ON,
114 "smpl": Crashreport.BOOT_REASON_RTC_ALARM,
115 "other": "whatever",
116 }
117
Mitja Nikolaus7dc86722018-11-27 14:57:39 +0100118 DEFAULT_DUMMY_LOG_FILE_DIRECTORY = os.path.join("resources", "test")
119
Mitja Nikolaus6e118472018-10-04 11:15:29 +0200120 DEFAULT_DUMMY_LOG_FILE_PATH = os.path.join(
Franz-Xaver Geiger38a66bc2018-10-09 14:52:26 +0200121 DEFAULT_DUMMY_LOG_FILE_DIRECTORY, "test_logfile.zip"
Mitja Nikolaus6e118472018-10-04 11:15:29 +0200122 )
123
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200124 @staticmethod
125 def _update_copy(original, update):
126 """Merge fields of update into a copy of original."""
127 data = original.copy()
128 data.update(update)
129 return data
130
131 @staticmethod
132 def device_register_data(**kwargs):
133 """Return the data required to register a device.
134
135 Use the values passed as keyword arguments or default to the ones
136 from `Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES`.
137 """
138 return Dummy._update_copy(
139 Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES, kwargs
140 )
141
142 @staticmethod
143 def heartbeat_data(**kwargs):
144 """Return the data required to create a heartbeat.
145
146 Use the values passed as keyword arguments or default to the ones
147 from `Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES`.
148 """
149 return Dummy._update_copy(Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs)
150
151 @staticmethod
152 def crashreport_data(report_type: Optional[str] = None, **kwargs):
153 """Return the data required to create a crashreport.
154
155 Use the values passed as keyword arguments or default to the ones
156 from `Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES`.
157
158 Args:
159 report_type: A valid value from
160 `Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.keys()` that will
161 define the boot reason if not explicitly defined in the
162 keyword arguments already.
163 """
164 data = Dummy._update_copy(
Mitja Nikolaus7dc86722018-11-27 14:57:39 +0100165 Dummy.DEFAULT_DUMMY_CRASHREPORT_VALUES, kwargs
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200166 )
167 if report_type and "boot_reason" not in kwargs:
168 if report_type not in Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP:
169 raise InvalidCrashTypeError(report_type)
170 data["boot_reason"] = Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.get(
171 report_type
172 )
173 return data
174
Mitja Nikolaus7dc86722018-11-27 14:57:39 +0100175 @staticmethod
176 def create_dummy_user(**kwargs):
177 """Create a dummy user instance.
178
179 The dummy instance is created and saved to the database.
180 Args:
181 **kwargs:
182 Optional arguments to extend/overwrite the default values.
183
184 Returns: The created user instance.
185
186 """
187 entity = User(
188 **Dummy._update_copy(Dummy.DEFAULT_DUMMY_USER_VALUES, kwargs)
189 )
190 entity.save()
191 return entity
192
193 @staticmethod
194 def create_dummy_device(user, **kwargs):
195 """Create a dummy device instance.
196
197 The dummy instance is created and saved to the database.
198 Args:
199 user: The user instance that the device should relate to
200 **kwargs:
201 Optional arguments to extend/overwrite the default values.
202
203 Returns: The created device instance.
204
205 """
206 entity = Device(
207 user=user,
208 **Dummy._update_copy(Dummy.DEFAULT_DUMMY_DEVICE_VALUES, kwargs)
209 )
210 entity.save()
211 return entity
212
213 @staticmethod
214 def create_dummy_report(report_type, device, **kwargs):
215 """Create a dummy report instance of the given report class type.
216
217 The dummy instance is created and saved to the database.
218 Args:
219 report_type: The class of the report type to be created.
220 user: The device instance that the heartbeat should relate to
221 **kwargs:
222 Optional arguments to extend/overwrite the default values.
223
224 Returns: The created report instance.
225
226 Raises:
227 RuntimeError: If report_type is not a report class type.
228
229 """
230 if report_type == HeartBeat:
231 entity = HeartBeat(
232 device=device,
233 **Dummy._update_copy(
234 Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs
235 )
236 )
237 elif report_type == Crashreport:
238 entity = Crashreport(
239 device=device,
240 **Dummy._update_copy(
241 Dummy.DEFAULT_DUMMY_CRASHREPORT_VALUES, kwargs
242 )
243 )
244 else:
245 raise RuntimeError(
246 "No dummy report instance can be created for {}".format(
247 report_type.__name__
248 )
249 )
250 entity.save()
251 return entity
252
253 @staticmethod
254 def create_dummy_log_file(crashreport, **kwargs):
255 """Create a dummy log file instance.
256
257 The dummy instance is created and saved to the database.
258
259 Args:
260 crashreport: The crashreport that the log file belongs to.
261 **kwargs: Optional arguments to extend/overwrite the default values.
262
263 Returns: The created log file instance.
264
265 """
266 entity = LogFile(
267 crashreport=crashreport,
268 **Dummy._update_copy(Dummy.DEFAULT_DUMMY_LOG_FILE_VALUES, kwargs)
269 )
270
271 entity.save()
272 return entity
273
274 @staticmethod
275 def read_logfile_contents(path_to_zipfile, logfile_name):
276 """Read bytes of a zipped logfile."""
277 archive = zipfile.ZipFile(path_to_zipfile, "r")
278 return archive.read(logfile_name)
279
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200280
281class HiccupCrashreportsAPITestCase(APITestCase):
282 """Base class that offers a device registration method."""
283
284 REGISTER_DEVICE_URL = "api_v1_register_device"
285
286 def setUp(self):
Mitja Nikolause0e83772018-11-05 10:00:53 +0100287 """Create a Fairphone staff user for accessing the API.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200288
289 The APIClient that can be used to make authenticated requests to the
Mitja Nikolause0e83772018-11-05 10:00:53 +0100290 server is stored in self.fp_staff_client.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200291 """
Mitja Nikolause0e83772018-11-05 10:00:53 +0100292 fp_staff_group = Group.objects.get(name=FP_STAFF_GROUP_NAME)
293 fp_staff_user = User.objects.create_user(
294 "fp_staff", "somebody@fairphone.com", "thepassword"
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200295 )
Mitja Nikolause0e83772018-11-05 10:00:53 +0100296 fp_staff_user.groups.add(fp_staff_group)
297 self.fp_staff_client = APIClient()
298 self.fp_staff_client.force_login(fp_staff_user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200299
300 def _register_device(self, **kwargs):
301 """Register a new device.
302
303 Arguments:
304 **kwargs: The data to pass the dummy data creation
305 method `Dummy.device_register_data`.
306 Returns:
307 (UUID, APIClient, str): The uuid of the new device as well as an
308 authentication token and the associated user with credentials.
309
310 """
311 data = Dummy.device_register_data(**kwargs)
312 response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
313 self.assertEqual(response.status_code, status.HTTP_200_OK)
314
315 uuid = response.data["uuid"]
316 token = response.data["token"]
317 user = APIClient()
318 user.credentials(HTTP_AUTHORIZATION="Token " + token)
319
320 return uuid, user, token