blob: 279320bad17941c59dcbcbf9dc683e5be53fb826 [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
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02004import zipfile
5
6import pytz
7from django.contrib.auth.models import Group
8from rest_framework import status
9from rest_framework.authtoken.models import Token
10from rest_framework.test import APITestCase, APIClient
11
12from crashreport_stats.models import (
13 Version,
14 VersionDaily,
15 RadioVersion,
16 RadioVersionDaily,
17 StatsMetadata,
18)
19
20from crashreports.models import Crashreport, Device, HeartBeat, LogFile, User
21from hiccup.allauth_adapters import FP_STAFF_GROUP_NAME
22
23
24class Dummy:
25 """Class for creating dummy instances for testing."""
26
27 # Valid unique entries
28 BUILD_FINGERPRINTS = [
29 (
30 "Fairphone/FP2/FP2:5.1/FP2/r4275.1_FP2_gms76_1.13.0"
31 ":user/release-keys"
32 ),
33 (
34 "Fairphone/FP2/FP2:5.1.1/FP2-gms75.1.13.0/FP2-gms75.1.13.0"
35 ":user/release-keys"
36 ),
37 (
38 "Fairphone/FP2/FP2:6.0.1/FP2-gms-18.04.1/FP2-gms-18.04.1"
39 ":user/release-keys"
40 ),
41 ("Fairphone/FP2/FP2:7.1.2/18.07.2/gms-7480c31d:user/release-keys"),
42 ]
43 RADIO_VERSIONS = [
44 "4437.1-FP2-0-07",
45 "4437.1-FP2-0-08",
46 "4437.1-FP2-0-09",
47 "4437.1-FP2-0-10",
48 ]
Mitja Nikolaus7e41c912018-09-21 14:54:26 +020049 UUIDs = ["e1c0cc95-ab8d-461a-a768-cb8d9d7fdb04"]
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020050
Mitja Nikolause0df5a32018-09-21 15:59:54 +020051 USERNAMES = ["testuser1", "testuser2", "testuser3"]
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020052
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",
Franz-Xaver Geiger38a66bc2018-10-09 14:52:26 +0200108 "logfile": "test_logfile.zip",
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200109 }
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
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200223 def create_dummy_version(version_type=Version, **kwargs):
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200224 """Create a dummy version instance.
225
226 The dummy instance is created and saved to the database.
227 Args:
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200228 version_type: The class of the version type to be created.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200229 **kwargs:
230 Optional arguments to extend/overwrite the default values.
231
232 Returns: The created version instance.
233
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200234 Raises:
235 ValueError: If version_type is not a valid version class type.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200236
237 """
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200238 if version_type == Version:
239 entity = Version(
240 **Dummy.update_copy(Dummy.DEFAULT_DUMMY_VERSION_VALUES, kwargs)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200241 )
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200242 elif version_type == RadioVersion:
243 entity = RadioVersion(
244 **Dummy.update_copy(
245 Dummy.DEFAULT_DUMMY_RADIO_VERSION_VALUES, kwargs
246 )
247 )
248 else:
249 raise ValueError(
250 "No dummy version instance can be created for {}".format(
251 version_type.__name__
252 )
253 )
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200254 entity.save()
255 return entity
256
257 @staticmethod
258 def create_dummy_daily_version(version, **kwargs):
259 """Create a dummy daily version instance.
260
261 The dummy instance is created and saved to the database.
262 Args:
263 **kwargs:
264 Optional arguments to extend/overwrite the default values.
265
266 Returns: The created daily version instance.
267
268 """
269 entity = VersionDaily(
270 version=version,
271 **Dummy.update_copy(
272 Dummy.DEFAULT_DUMMY_VERSION_DAILY_VALUES, kwargs
273 )
274 )
275 entity.save()
276 return entity
277
278 @staticmethod
279 def create_dummy_daily_radio_version(version, **kwargs):
280 """Create a dummy daily radio version instance.
281
282 The dummy instance is created and saved to the database.
283 Args:
284 **kwargs:
285 Optional arguments to extend/overwrite the default values.
286
287 Returns: The created daily radio version instance.
288
289 """
290 entity = RadioVersionDaily(
291 version=version,
292 **Dummy.update_copy(
293 Dummy.DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES, kwargs
294 )
295 )
296 entity.save()
297 return entity
298
299 @staticmethod
300 def create_dummy_stats_metadata(**kwargs):
301 """Create a dummy stats metadata instance.
302
303 The dummy instance is created and saved to the database.
304 Args:
305 **kwargs:
306 Optional arguments to extend/overwrite the default values.
307
308 Returns: The created stats metadata instance.
309
310 """
311 entity = StatsMetadata(
312 **Dummy.update_copy(
313 Dummy.DEFAULT_DUMMY_STATSMETADATA_VALUES, kwargs
314 )
315 )
316 entity.save()
317 return entity
318
319
320class HiccupStatsAPITestCase(APITestCase):
321 """Abstract class for Hiccup stats REST API test cases to inherit from."""
322
323 @classmethod
324 def setUpTestData(cls): # noqa: N802
325 """Create an admin and two client users for accessing the API.
326
327 The APIClient that can be used to make authenticated requests as
328 admin user is stored in self.admin. A client which is related to a
329 user that is part of the Fairphone staff group is stored in
330 self.fp_staff_client. A client which is related to a device owner
331 user is stored in self.device_owner_client.
332 """
333 admin_user = User.objects.create_superuser(
334 "somebody", "somebody@example.com", "thepassword"
335 )
336 cls.admin = APIClient()
Mitja Nikolaus0b1aa8c2018-09-14 15:33:29 +0200337 cls.admin.force_login(admin_user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200338
Mitja Nikolausd6907dd2018-10-17 14:13:06 +0200339 fp_staff_group = Group.objects.get(name=FP_STAFF_GROUP_NAME)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200340 fp_staff_user = User.objects.create_user(
341 "fp_staff", "somebody@fairphone.com", "thepassword"
342 )
343 fp_staff_user.groups.add(fp_staff_group)
344 cls.fp_staff_client = APIClient()
345 cls.fp_staff_client.force_login(fp_staff_user)
346
347 cls.device_owner_user = User.objects.create_user(
348 "device_owner", "somebody@somemail.com", "thepassword"
349 )
350 Token.objects.create(user=cls.device_owner_user)
351 cls.device_owner_device = Dummy.create_dummy_device(
352 user=cls.device_owner_user
353 )
354 cls.device_owner_client = APIClient()
355 cls.device_owner_client.credentials(
356 HTTP_AUTHORIZATION="Token " + cls.device_owner_user.auth_token.key
357 )
358
359 def _assert_get_as_admin_user_succeeds(
360 self, url, expected_status=status.HTTP_200_OK
361 ):
362 response = self.admin.get(url)
363 self.assertEqual(response.status_code, expected_status)
364
365 def _assert_get_as_fp_staff_succeeds(
366 self, url, expected_status=status.HTTP_200_OK
367 ):
368 response = self.fp_staff_client.get(url)
369 self.assertEqual(response.status_code, expected_status)
370
371 def _assert_get_without_authentication_fails(
372 self, url, expected_status=status.HTTP_401_UNAUTHORIZED
373 ):
374 response = self.client.get(url)
375 self.assertEqual(response.status_code, expected_status)
376
377 def _assert_get_as_device_owner_fails(
378 self, url, expected_status=status.HTTP_403_FORBIDDEN
379 ):
380 response = self.device_owner_client.get(url)
381 self.assertEqual(response.status_code, expected_status)