blob: c7c85f85d6f57ac0529cefd02cb7cd92dde09bd8 [file] [log] [blame]
"""Utility functions shared by all crashreport stats tests."""
from datetime import datetime, date
import os
import zipfile
import pytz
from django.contrib.auth.models import Group
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework.test import APITestCase, APIClient
from crashreport_stats.models import (
Version,
VersionDaily,
RadioVersion,
RadioVersionDaily,
StatsMetadata,
)
from crashreports.models import Crashreport, Device, HeartBeat, LogFile, User
from hiccup.allauth_adapters import FP_STAFF_GROUP_NAME
class Dummy:
"""Class for creating dummy instances for testing."""
# Valid unique entries
BUILD_FINGERPRINTS = [
(
"Fairphone/FP2/FP2:5.1/FP2/r4275.1_FP2_gms76_1.13.0"
":user/release-keys"
),
(
"Fairphone/FP2/FP2:5.1.1/FP2-gms75.1.13.0/FP2-gms75.1.13.0"
":user/release-keys"
),
(
"Fairphone/FP2/FP2:6.0.1/FP2-gms-18.04.1/FP2-gms-18.04.1"
":user/release-keys"
),
("Fairphone/FP2/FP2:7.1.2/18.07.2/gms-7480c31d:user/release-keys"),
]
RADIO_VERSIONS = [
"4437.1-FP2-0-07",
"4437.1-FP2-0-08",
"4437.1-FP2-0-09",
"4437.1-FP2-0-10",
]
UUIDs = ["e1c0cc95-ab8d-461a-a768-cb8d9d7fdb04"]
USERNAMES = ["testuser1", "testuser2", "testuser3"]
DATES = [date(2018, 3, 19), date(2018, 3, 26), date(2018, 5, 1)]
DEFAULT_DUMMY_VERSION_VALUES = {
"build_fingerprint": BUILD_FINGERPRINTS[0],
"first_seen_on": DATES[1],
"released_on": DATES[0],
"is_beta_release": False,
"is_official_release": True,
}
DEFAULT_DUMMY_VERSION_DAILY_VALUES = {"date": DATES[1]}
DEFAULT_DUMMY_RADIO_VERSION_VALUES = {
"radio_version": RADIO_VERSIONS[0],
"first_seen_on": DATES[1],
"released_on": DATES[0],
}
DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES = {"date": DATES[1]}
DEFAULT_DUMMY_STATSMETADATA_VALUES = {
"updated_at": datetime(2018, 6, 15, 2, 12, 24, tzinfo=pytz.utc)
}
DEFAULT_DUMMY_DEVICE_VALUES = {
"board_date": datetime(2015, 12, 15, 1, 23, 45, tzinfo=pytz.utc),
"chipset": "Qualcomm MSM8974PRO-AA",
"token": "64111c62d521fb4724454ca6dea27e18f93ef56e",
}
DEFAULT_DUMMY_USER_VALUES = {"username": USERNAMES[0]}
DEFAULT_DUMMY_HEARTBEAT_VALUES = {
"app_version": 10100,
"uptime": (
"up time: 16 days, 21:49:56, idle time: 5 days, 20:55:04, "
"sleep time: 10 days, 20:46:27"
),
"build_fingerprint": BUILD_FINGERPRINTS[0],
"radio_version": RADIO_VERSIONS[0],
"date": datetime(2018, 3, 19, 12, 0, 0, tzinfo=pytz.utc),
}
DEFAULT_DUMMY_CRASHREPORT_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
DEFAULT_DUMMY_CRASHREPORT_VALUES.update(
{
"is_fake_report": 0,
"boot_reason": Crashreport.BOOT_REASON_UNKOWN,
"power_on_reason": "it was powered on",
"power_off_reason": "something happened and it went off",
}
)
DEFAULT_DUMMY_LOG_FILE_VALUES = {
"logfile_type": "last_kmsg",
"logfile": os.path.join("resources", "test", "test_logfile.zip"),
}
DEFAULT_DUMMY_LOG_FILE_NAME = "dmesg.log"
@staticmethod
def update_copy(original, update):
"""Merge fields of update into a copy of original."""
data = original.copy()
data.update(update)
return data
@staticmethod
def create_dummy_user(**kwargs):
"""Create a dummy user instance.
The dummy instance is created and saved to the database.
Args:
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created user instance.
"""
entity = User(
**Dummy.update_copy(Dummy.DEFAULT_DUMMY_USER_VALUES, kwargs)
)
entity.save()
return entity
@staticmethod
def create_dummy_device(user, **kwargs):
"""Create a dummy device instance.
The dummy instance is created and saved to the database.
Args:
user: The user instance that the device should relate to
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created device instance.
"""
entity = Device(
user=user,
**Dummy.update_copy(Dummy.DEFAULT_DUMMY_DEVICE_VALUES, kwargs)
)
entity.save()
return entity
@staticmethod
def create_dummy_report(report_type, device, **kwargs):
"""Create a dummy report instance of the given report class type.
The dummy instance is created and saved to the database.
Args:
report_type: The class of the report type to be created.
user: The device instance that the heartbeat should relate to
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created report instance.
"""
if report_type == HeartBeat:
entity = HeartBeat(
device=device,
**Dummy.update_copy(
Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs
)
)
elif report_type == Crashreport:
entity = Crashreport(
device=device,
**Dummy.update_copy(
Dummy.DEFAULT_DUMMY_CRASHREPORT_VALUES, kwargs
)
)
else:
raise RuntimeError(
"No dummy report instance can be created for {}".format(
report_type.__name__
)
)
entity.save()
return entity
@staticmethod
def create_dummy_log_file(crashreport, **kwargs):
"""Create a dummy log file instance.
The dummy instance is created and saved to the database.
Args:
crashreport: The crashreport that the log file belongs to.
**kwargs: Optional arguments to extend/overwrite the default values.
Returns: The created log file instance.
"""
entity = LogFile(
crashreport=crashreport,
**Dummy.update_copy(Dummy.DEFAULT_DUMMY_LOG_FILE_VALUES, kwargs)
)
entity.save()
return entity
@staticmethod
def read_logfile_contents(path_to_zipfile, logfile_name):
"""Read bytes of a zipped logfile."""
archive = zipfile.ZipFile(path_to_zipfile, "r")
return archive.read(logfile_name)
@staticmethod
def create_dummy_version(**kwargs):
"""Create a dummy version instance.
The dummy instance is created and saved to the database.
Args:
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created version instance.
"""
entity = Version(
**Dummy.update_copy(Dummy.DEFAULT_DUMMY_VERSION_VALUES, kwargs)
)
entity.save()
return entity
@staticmethod
def create_dummy_radio_version(**kwargs):
"""Create a dummy radio version instance.
The dummy instance is created and saved to the database.
Args:
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created radio version instance.
"""
entity = RadioVersion(
**Dummy.update_copy(
Dummy.DEFAULT_DUMMY_RADIO_VERSION_VALUES, kwargs
)
)
entity.save()
return entity
@staticmethod
def create_dummy_daily_version(version, **kwargs):
"""Create a dummy daily version instance.
The dummy instance is created and saved to the database.
Args:
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created daily version instance.
"""
entity = VersionDaily(
version=version,
**Dummy.update_copy(
Dummy.DEFAULT_DUMMY_VERSION_DAILY_VALUES, kwargs
)
)
entity.save()
return entity
@staticmethod
def create_dummy_daily_radio_version(version, **kwargs):
"""Create a dummy daily radio version instance.
The dummy instance is created and saved to the database.
Args:
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created daily radio version instance.
"""
entity = RadioVersionDaily(
version=version,
**Dummy.update_copy(
Dummy.DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES, kwargs
)
)
entity.save()
return entity
@staticmethod
def create_dummy_stats_metadata(**kwargs):
"""Create a dummy stats metadata instance.
The dummy instance is created and saved to the database.
Args:
**kwargs:
Optional arguments to extend/overwrite the default values.
Returns: The created stats metadata instance.
"""
entity = StatsMetadata(
**Dummy.update_copy(
Dummy.DEFAULT_DUMMY_STATSMETADATA_VALUES, kwargs
)
)
entity.save()
return entity
class HiccupStatsAPITestCase(APITestCase):
"""Abstract class for Hiccup stats REST API test cases to inherit from."""
@classmethod
def setUpTestData(cls): # noqa: N802
"""Create an admin and two client users for accessing the API.
The APIClient that can be used to make authenticated requests as
admin user is stored in self.admin. A client which is related to a
user that is part of the Fairphone staff group is stored in
self.fp_staff_client. A client which is related to a device owner
user is stored in self.device_owner_client.
"""
admin_user = User.objects.create_superuser(
"somebody", "somebody@example.com", "thepassword"
)
cls.admin = APIClient()
cls.admin.force_login(admin_user)
fp_staff_group = Group(name=FP_STAFF_GROUP_NAME)
fp_staff_group.save()
fp_staff_user = User.objects.create_user(
"fp_staff", "somebody@fairphone.com", "thepassword"
)
fp_staff_user.groups.add(fp_staff_group)
cls.fp_staff_client = APIClient()
cls.fp_staff_client.force_login(fp_staff_user)
cls.device_owner_user = User.objects.create_user(
"device_owner", "somebody@somemail.com", "thepassword"
)
Token.objects.create(user=cls.device_owner_user)
cls.device_owner_device = Dummy.create_dummy_device(
user=cls.device_owner_user
)
cls.device_owner_client = APIClient()
cls.device_owner_client.credentials(
HTTP_AUTHORIZATION="Token " + cls.device_owner_user.auth_token.key
)
def _assert_get_as_admin_user_succeeds(
self, url, expected_status=status.HTTP_200_OK
):
response = self.admin.get(url)
self.assertEqual(response.status_code, expected_status)
def _assert_get_as_fp_staff_succeeds(
self, url, expected_status=status.HTTP_200_OK
):
response = self.fp_staff_client.get(url)
self.assertEqual(response.status_code, expected_status)
def _assert_get_without_authentication_fails(
self, url, expected_status=status.HTTP_401_UNAUTHORIZED
):
response = self.client.get(url)
self.assertEqual(response.status_code, expected_status)
def _assert_get_as_device_owner_fails(
self, url, expected_status=status.HTTP_403_FORBIDDEN
):
response = self.device_owner_client.get(url)
self.assertEqual(response.status_code, expected_status)