blob: 04697a0cef77c3fbd5c53ed52c64132267cf7767 [file] [log] [blame]
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02001"""Utility functions shared by all crashreport stats tests."""
2
3from datetime import datetime, date
4import os
5import zipfile
6
7import pytz
8from django.contrib.auth.models import Group
9from rest_framework import status
10from rest_framework.authtoken.models import Token
11from rest_framework.test import APITestCase, APIClient
12
13from crashreport_stats.models import (
14 Version,
15 VersionDaily,
16 RadioVersion,
17 RadioVersionDaily,
18 StatsMetadata,
19)
20
21from crashreports.models import Crashreport, Device, HeartBeat, LogFile, User
22from hiccup.allauth_adapters import FP_STAFF_GROUP_NAME
23
24
25class Dummy:
26 """Class for creating dummy instances for testing."""
27
28 # Valid unique entries
29 BUILD_FINGERPRINTS = [
30 (
31 "Fairphone/FP2/FP2:5.1/FP2/r4275.1_FP2_gms76_1.13.0"
32 ":user/release-keys"
33 ),
34 (
35 "Fairphone/FP2/FP2:5.1.1/FP2-gms75.1.13.0/FP2-gms75.1.13.0"
36 ":user/release-keys"
37 ),
38 (
39 "Fairphone/FP2/FP2:6.0.1/FP2-gms-18.04.1/FP2-gms-18.04.1"
40 ":user/release-keys"
41 ),
42 ("Fairphone/FP2/FP2:7.1.2/18.07.2/gms-7480c31d:user/release-keys"),
43 ]
44 RADIO_VERSIONS = [
45 "4437.1-FP2-0-07",
46 "4437.1-FP2-0-08",
47 "4437.1-FP2-0-09",
48 "4437.1-FP2-0-10",
49 ]
50
51 USERNAMES = ["testuser1", "testuser2"]
52
53 DATES = [date(2018, 3, 19), date(2018, 3, 26), date(2018, 5, 1)]
54
55 DEFAULT_DUMMY_VERSION_VALUES = {
56 "build_fingerprint": BUILD_FINGERPRINTS[0],
57 "first_seen_on": DATES[1],
58 "released_on": DATES[0],
59 "is_beta_release": False,
60 "is_official_release": True,
61 }
62
63 DEFAULT_DUMMY_VERSION_DAILY_VALUES = {"date": DATES[1]}
64
65 DEFAULT_DUMMY_RADIO_VERSION_VALUES = {
66 "radio_version": RADIO_VERSIONS[0],
67 "first_seen_on": DATES[1],
68 "released_on": DATES[0],
69 }
70
71 DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES = {"date": DATES[1]}
72
73 DEFAULT_DUMMY_STATSMETADATA_VALUES = {
74 "updated_at": datetime(2018, 6, 15, 2, 12, 24, tzinfo=pytz.utc)
75 }
76
77 DEFAULT_DUMMY_DEVICE_VALUES = {
78 "board_date": datetime(2015, 12, 15, 1, 23, 45, tzinfo=pytz.utc),
79 "chipset": "Qualcomm MSM8974PRO-AA",
80 "token": "64111c62d521fb4724454ca6dea27e18f93ef56e",
81 }
82
83 DEFAULT_DUMMY_USER_VALUES = {"username": USERNAMES[0]}
84
85 DEFAULT_DUMMY_HEARTBEAT_VALUES = {
86 "app_version": 10100,
87 "uptime": (
88 "up time: 16 days, 21:49:56, idle time: 5 days, 20:55:04, "
89 "sleep time: 10 days, 20:46:27"
90 ),
91 "build_fingerprint": BUILD_FINGERPRINTS[0],
92 "radio_version": RADIO_VERSIONS[0],
93 "date": datetime(2018, 3, 19, 12, 0, 0, tzinfo=pytz.utc),
94 }
95
96 DEFAULT_DUMMY_CRASHREPORT_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
97 DEFAULT_DUMMY_CRASHREPORT_VALUES.update(
98 {
99 "is_fake_report": 0,
100 "boot_reason": Crashreport.BOOT_REASON_UNKOWN,
101 "power_on_reason": "it was powered on",
102 "power_off_reason": "something happened and it went off",
103 }
104 )
105
106 DEFAULT_DUMMY_LOG_FILE_VALUES = {
107 "logfile_type": "last_kmsg",
108 "logfile": os.path.join("resources", "test", "test_logfile.zip"),
109 }
110
111 DEFAULT_DUMMY_LOG_FILE_NAME = "dmesg.log"
112
113 @staticmethod
114 def update_copy(original, update):
115 """Merge fields of update into a copy of original."""
116 data = original.copy()
117 data.update(update)
118 return data
119
120 @staticmethod
121 def create_dummy_user(**kwargs):
122 """Create a dummy user instance.
123
124 The dummy instance is created and saved to the database.
125 Args:
126 **kwargs:
127 Optional arguments to extend/overwrite the default values.
128
129 Returns: The created user instance.
130
131 """
132 entity = User(
133 **Dummy.update_copy(Dummy.DEFAULT_DUMMY_USER_VALUES, kwargs)
134 )
135 entity.save()
136 return entity
137
138 @staticmethod
139 def create_dummy_device(user, **kwargs):
140 """Create a dummy device instance.
141
142 The dummy instance is created and saved to the database.
143 Args:
144 user: The user instance that the device should relate to
145 **kwargs:
146 Optional arguments to extend/overwrite the default values.
147
148 Returns: The created device instance.
149
150 """
151 entity = Device(
152 user=user,
153 **Dummy.update_copy(Dummy.DEFAULT_DUMMY_DEVICE_VALUES, kwargs)
154 )
155 entity.save()
156 return entity
157
158 @staticmethod
159 def create_dummy_report(report_type, device, **kwargs):
160 """Create a dummy report instance of the given report class type.
161
162 The dummy instance is created and saved to the database.
163 Args:
164 report_type: The class of the report type to be created.
165 user: The device instance that the heartbeat should relate to
166 **kwargs:
167 Optional arguments to extend/overwrite the default values.
168
169 Returns: The created report instance.
170
171 """
172 if report_type == HeartBeat:
173 entity = HeartBeat(
174 device=device,
175 **Dummy.update_copy(
176 Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs
177 )
178 )
179 elif report_type == Crashreport:
180 entity = Crashreport(
181 device=device,
182 **Dummy.update_copy(
183 Dummy.DEFAULT_DUMMY_CRASHREPORT_VALUES, kwargs
184 )
185 )
186 else:
187 raise RuntimeError(
188 "No dummy report instance can be created for {}".format(
189 report_type.__name__
190 )
191 )
192 entity.save()
193 return entity
194
195 @staticmethod
196 def create_dummy_log_file(crashreport, **kwargs):
197 """Create a dummy log file instance.
198
199 The dummy instance is created and saved to the database.
200
201 Args:
202 crashreport: The crashreport that the log file belongs to.
203 **kwargs: Optional arguments to extend/overwrite the default values.
204
205 Returns: The created log file instance.
206
207 """
208 entity = LogFile(
209 crashreport=crashreport,
210 **Dummy.update_copy(Dummy.DEFAULT_DUMMY_LOG_FILE_VALUES, kwargs)
211 )
212
213 entity.save()
214 return entity
215
216 @staticmethod
217 def read_logfile_contents(path_to_zipfile, logfile_name):
218 """Read bytes of a zipped logfile."""
219 archive = zipfile.ZipFile(path_to_zipfile, "r")
220 return archive.read(logfile_name)
221
222 @staticmethod
223 def create_dummy_version(**kwargs):
224 """Create a dummy version instance.
225
226 The dummy instance is created and saved to the database.
227 Args:
228 **kwargs:
229 Optional arguments to extend/overwrite the default values.
230
231 Returns: The created version instance.
232
233 """
234 entity = Version(
235 **Dummy.update_copy(Dummy.DEFAULT_DUMMY_VERSION_VALUES, kwargs)
236 )
237 entity.save()
238 return entity
239
240 @staticmethod
241 def create_dummy_radio_version(**kwargs):
242 """Create a dummy radio version instance.
243
244 The dummy instance is created and saved to the database.
245 Args:
246 **kwargs:
247 Optional arguments to extend/overwrite the default values.
248
249 Returns: The created radio version instance.
250
251 """
252 entity = RadioVersion(
253 **Dummy.update_copy(
254 Dummy.DEFAULT_DUMMY_RADIO_VERSION_VALUES, kwargs
255 )
256 )
257 entity.save()
258 return entity
259
260 @staticmethod
261 def create_dummy_daily_version(version, **kwargs):
262 """Create a dummy daily version instance.
263
264 The dummy instance is created and saved to the database.
265 Args:
266 **kwargs:
267 Optional arguments to extend/overwrite the default values.
268
269 Returns: The created daily version instance.
270
271 """
272 entity = VersionDaily(
273 version=version,
274 **Dummy.update_copy(
275 Dummy.DEFAULT_DUMMY_VERSION_DAILY_VALUES, kwargs
276 )
277 )
278 entity.save()
279 return entity
280
281 @staticmethod
282 def create_dummy_daily_radio_version(version, **kwargs):
283 """Create a dummy daily radio version instance.
284
285 The dummy instance is created and saved to the database.
286 Args:
287 **kwargs:
288 Optional arguments to extend/overwrite the default values.
289
290 Returns: The created daily radio version instance.
291
292 """
293 entity = RadioVersionDaily(
294 version=version,
295 **Dummy.update_copy(
296 Dummy.DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES, kwargs
297 )
298 )
299 entity.save()
300 return entity
301
302 @staticmethod
303 def create_dummy_stats_metadata(**kwargs):
304 """Create a dummy stats metadata instance.
305
306 The dummy instance is created and saved to the database.
307 Args:
308 **kwargs:
309 Optional arguments to extend/overwrite the default values.
310
311 Returns: The created stats metadata instance.
312
313 """
314 entity = StatsMetadata(
315 **Dummy.update_copy(
316 Dummy.DEFAULT_DUMMY_STATSMETADATA_VALUES, kwargs
317 )
318 )
319 entity.save()
320 return entity
321
322
323class HiccupStatsAPITestCase(APITestCase):
324 """Abstract class for Hiccup stats REST API test cases to inherit from."""
325
326 @classmethod
327 def setUpTestData(cls): # noqa: N802
328 """Create an admin and two client users for accessing the API.
329
330 The APIClient that can be used to make authenticated requests as
331 admin user is stored in self.admin. A client which is related to a
332 user that is part of the Fairphone staff group is stored in
333 self.fp_staff_client. A client which is related to a device owner
334 user is stored in self.device_owner_client.
335 """
336 admin_user = User.objects.create_superuser(
337 "somebody", "somebody@example.com", "thepassword"
338 )
339 cls.admin = APIClient()
340 cls.admin.force_authenticate(admin_user)
341
342 fp_staff_group = Group(name=FP_STAFF_GROUP_NAME)
343 fp_staff_group.save()
344 fp_staff_user = User.objects.create_user(
345 "fp_staff", "somebody@fairphone.com", "thepassword"
346 )
347 fp_staff_user.groups.add(fp_staff_group)
348 cls.fp_staff_client = APIClient()
349 cls.fp_staff_client.force_login(fp_staff_user)
350
351 cls.device_owner_user = User.objects.create_user(
352 "device_owner", "somebody@somemail.com", "thepassword"
353 )
354 Token.objects.create(user=cls.device_owner_user)
355 cls.device_owner_device = Dummy.create_dummy_device(
356 user=cls.device_owner_user
357 )
358 cls.device_owner_client = APIClient()
359 cls.device_owner_client.credentials(
360 HTTP_AUTHORIZATION="Token " + cls.device_owner_user.auth_token.key
361 )
362
363 def _assert_get_as_admin_user_succeeds(
364 self, url, expected_status=status.HTTP_200_OK
365 ):
366 response = self.admin.get(url)
367 self.assertEqual(response.status_code, expected_status)
368
369 def _assert_get_as_fp_staff_succeeds(
370 self, url, expected_status=status.HTTP_200_OK
371 ):
372 response = self.fp_staff_client.get(url)
373 self.assertEqual(response.status_code, expected_status)
374
375 def _assert_get_without_authentication_fails(
376 self, url, expected_status=status.HTTP_401_UNAUTHORIZED
377 ):
378 response = self.client.get(url)
379 self.assertEqual(response.status_code, expected_status)
380
381 def _assert_get_as_device_owner_fails(
382 self, url, expected_status=status.HTTP_403_FORBIDDEN
383 ):
384 response = self.device_owner_client.get(url)
385 self.assertEqual(response.status_code, expected_status)