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