blob: 8db1d0f3bc387679b717fedc1383757921e94175 [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
Mitja Nikolause0e83772018-11-05 10:00:53 +0100325 """Create client users for accessing the API.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200326
327 The APIClient that can be used to make authenticated requests as
Mitja Nikolause0e83772018-11-05 10:00:53 +0100328 Fairphone staff user is stored in self.fp_staff_client. Additionally, a
329 client which is related to a device owner user is stored in
330 self.device_owner_client.
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200331 """
Mitja Nikolausd6907dd2018-10-17 14:13:06 +0200332 fp_staff_group = Group.objects.get(name=FP_STAFF_GROUP_NAME)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200333 fp_staff_user = User.objects.create_user(
334 "fp_staff", "somebody@fairphone.com", "thepassword"
335 )
336 fp_staff_user.groups.add(fp_staff_group)
337 cls.fp_staff_client = APIClient()
338 cls.fp_staff_client.force_login(fp_staff_user)
339
340 cls.device_owner_user = User.objects.create_user(
341 "device_owner", "somebody@somemail.com", "thepassword"
342 )
343 Token.objects.create(user=cls.device_owner_user)
344 cls.device_owner_device = Dummy.create_dummy_device(
345 user=cls.device_owner_user
346 )
347 cls.device_owner_client = APIClient()
348 cls.device_owner_client.credentials(
349 HTTP_AUTHORIZATION="Token " + cls.device_owner_user.auth_token.key
350 )
351
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200352 def _assert_get_as_fp_staff_succeeds(
353 self, url, expected_status=status.HTTP_200_OK
354 ):
355 response = self.fp_staff_client.get(url)
356 self.assertEqual(response.status_code, expected_status)
357
358 def _assert_get_without_authentication_fails(
359 self, url, expected_status=status.HTTP_401_UNAUTHORIZED
360 ):
361 response = self.client.get(url)
362 self.assertEqual(response.status_code, expected_status)
363
364 def _assert_get_as_device_owner_fails(
365 self, url, expected_status=status.HTTP_403_FORBIDDEN
366 ):
367 response = self.device_owner_client.get(url)
368 self.assertEqual(response.status_code, expected_status)