blob: d8f0d9d2fc9ec3074c50cefa21f7cbbce0d609a7 [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
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200224 def create_dummy_version(version_type=Version, **kwargs):
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200225 """Create a dummy version instance.
226
227 The dummy instance is created and saved to the database.
228 Args:
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200229 version_type: The class of the version type to be created.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200230 **kwargs:
231 Optional arguments to extend/overwrite the default values.
232
233 Returns: The created version instance.
234
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200235 Raises:
236 ValueError: If version_type is not a valid version class type.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200237
238 """
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200239 if version_type == Version:
240 entity = Version(
241 **Dummy.update_copy(Dummy.DEFAULT_DUMMY_VERSION_VALUES, kwargs)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200242 )
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200243 elif version_type == RadioVersion:
244 entity = RadioVersion(
245 **Dummy.update_copy(
246 Dummy.DEFAULT_DUMMY_RADIO_VERSION_VALUES, kwargs
247 )
248 )
249 else:
250 raise ValueError(
251 "No dummy version instance can be created for {}".format(
252 version_type.__name__
253 )
254 )
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200255 entity.save()
256 return entity
257
258 @staticmethod
259 def create_dummy_daily_version(version, **kwargs):
260 """Create a dummy daily version instance.
261
262 The dummy instance is created and saved to the database.
263 Args:
264 **kwargs:
265 Optional arguments to extend/overwrite the default values.
266
267 Returns: The created daily version instance.
268
269 """
270 entity = VersionDaily(
271 version=version,
272 **Dummy.update_copy(
273 Dummy.DEFAULT_DUMMY_VERSION_DAILY_VALUES, kwargs
274 )
275 )
276 entity.save()
277 return entity
278
279 @staticmethod
280 def create_dummy_daily_radio_version(version, **kwargs):
281 """Create a dummy daily radio version instance.
282
283 The dummy instance is created and saved to the database.
284 Args:
285 **kwargs:
286 Optional arguments to extend/overwrite the default values.
287
288 Returns: The created daily radio version instance.
289
290 """
291 entity = RadioVersionDaily(
292 version=version,
293 **Dummy.update_copy(
294 Dummy.DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES, kwargs
295 )
296 )
297 entity.save()
298 return entity
299
300 @staticmethod
301 def create_dummy_stats_metadata(**kwargs):
302 """Create a dummy stats metadata instance.
303
304 The dummy instance is created and saved to the database.
305 Args:
306 **kwargs:
307 Optional arguments to extend/overwrite the default values.
308
309 Returns: The created stats metadata instance.
310
311 """
312 entity = StatsMetadata(
313 **Dummy.update_copy(
314 Dummy.DEFAULT_DUMMY_STATSMETADATA_VALUES, kwargs
315 )
316 )
317 entity.save()
318 return entity
319
320
321class HiccupStatsAPITestCase(APITestCase):
322 """Abstract class for Hiccup stats REST API test cases to inherit from."""
323
324 @classmethod
325 def setUpTestData(cls): # noqa: N802
326 """Create an admin and two client users for accessing the API.
327
328 The APIClient that can be used to make authenticated requests as
329 admin user is stored in self.admin. A client which is related to a
330 user that is part of the Fairphone staff group is stored in
331 self.fp_staff_client. A client which is related to a device owner
332 user is stored in self.device_owner_client.
333 """
334 admin_user = User.objects.create_superuser(
335 "somebody", "somebody@example.com", "thepassword"
336 )
337 cls.admin = APIClient()
Mitja Nikolaus0b1aa8c2018-09-14 15:33:29 +0200338 cls.admin.force_login(admin_user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200339
340 fp_staff_group = Group(name=FP_STAFF_GROUP_NAME)
341 fp_staff_group.save()
342 fp_staff_user = User.objects.create_user(
343 "fp_staff", "somebody@fairphone.com", "thepassword"
344 )
345 fp_staff_user.groups.add(fp_staff_group)
346 cls.fp_staff_client = APIClient()
347 cls.fp_staff_client.force_login(fp_staff_user)
348
349 cls.device_owner_user = User.objects.create_user(
350 "device_owner", "somebody@somemail.com", "thepassword"
351 )
352 Token.objects.create(user=cls.device_owner_user)
353 cls.device_owner_device = Dummy.create_dummy_device(
354 user=cls.device_owner_user
355 )
356 cls.device_owner_client = APIClient()
357 cls.device_owner_client.credentials(
358 HTTP_AUTHORIZATION="Token " + cls.device_owner_user.auth_token.key
359 )
360
361 def _assert_get_as_admin_user_succeeds(
362 self, url, expected_status=status.HTTP_200_OK
363 ):
364 response = self.admin.get(url)
365 self.assertEqual(response.status_code, expected_status)
366
367 def _assert_get_as_fp_staff_succeeds(
368 self, url, expected_status=status.HTTP_200_OK
369 ):
370 response = self.fp_staff_client.get(url)
371 self.assertEqual(response.status_code, expected_status)
372
373 def _assert_get_without_authentication_fails(
374 self, url, expected_status=status.HTTP_401_UNAUTHORIZED
375 ):
376 response = self.client.get(url)
377 self.assertEqual(response.status_code, expected_status)
378
379 def _assert_get_as_device_owner_fails(
380 self, url, expected_status=status.HTTP_403_FORBIDDEN
381 ):
382 response = self.device_owner_client.get(url)
383 self.assertEqual(response.status_code, expected_status)