blob: a204d42cff03ffffe541b0e954898896be09c359 [file] [log] [blame]
"""Test crashreport_stats models and the 'stats' command."""
from datetime import datetime, date
import pytz
from django.urls import reverse
from django.utils.http import urlencode
from rest_framework import status
from rest_framework.test import APITestCase, APIClient
from crashreport_stats.models import (
Version, VersionDaily, RadioVersion, RadioVersionDaily
)
from crashreports.models import User, Device
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']
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]
}
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_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': 'testuser'
}
@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_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
class _VersionTestCase(APITestCase):
"""Abstract class for version-related test cases to inherit from."""
# The attribute name characterising the unicity of a stats entry (the
# named identifier)
unique_entry_name = 'build_fingerprint'
# The collection of unique entries to post
unique_entries = Dummy.BUILD_FINGERPRINTS
# The URL to retrieve the stats entries from
endpoint_url = reverse('hiccup_stats_api_v1_versions')
@classmethod
def setUpTestData(cls): # noqa: N802
"""Create an admin user for accessing the API.
The APIClient that can be used to make authenticated requests to the
server is stored in self.admin.
"""
admin_user = User.objects.create_superuser(
'somebody', 'somebody@example.com', 'thepassword')
cls.admin = APIClient()
cls.admin.force_authenticate(admin_user)
@staticmethod
def _create_dummy_version(**kwargs):
return Dummy.create_dummy_version(**kwargs)
def _get_with_params(self, url, params):
return self.admin.get('{}?{}'.format(url, urlencode(params)))
def _assert_result_length_is(self, response, count):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('results', response.data)
self.assertIn('count', response.data)
self.assertEqual(response.data['count'], count)
self.assertEqual(len(response.data['results']), count)
def _assert_device_owner_has_no_get_access(self, entries_url):
# Create a user and device
user = Dummy.create_dummy_user()
device = Dummy.create_dummy_device(user=user)
# Create authenticated client
user = APIClient()
user.credentials(HTTP_AUTHORIZATION='Token ' + device.token)
# Try getting entries using the client
response = user.get(entries_url)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def _assert_filter_result_matches(self, filter_params, expected_result):
# List entities with filter
response = self._get_with_params(self.endpoint_url, filter_params)
# Expect only the single matching result to be returned
self._assert_result_length_is(response, 1)
self.assertEqual(response.data['results'][0][self.unique_entry_name],
getattr(expected_result, self.unique_entry_name))
class VersionTestCase(_VersionTestCase):
"""Test the Version and REST endpoint."""
def _create_version_entities(self):
versions = [
self._create_dummy_version(
**{self.unique_entry_name: unique_entry}
)
for unique_entry in self.unique_entries
]
return versions
def test_list_versions_without_authentication(self):
"""Test listing of versions without authentication."""
response = self.client.get(self.endpoint_url)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_list_versions_as_device_owner(self):
"""Test listing of versions as device owner."""
self._assert_device_owner_has_no_get_access(self.endpoint_url)
def test_list_versions_empty_database(self):
"""Test listing of versions on an empty database."""
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, 0)
def test_list_versions(self):
"""Test listing versions."""
versions = self._create_version_entities()
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, len(versions))
def test_filter_versions_by_unique_entry_name(self):
"""Test filtering versions by their unique entry name."""
versions = self._create_version_entities()
response = self.admin.get(self.endpoint_url)
# Listing all entities should return the correct result length
self._assert_result_length_is(response, len(versions))
# List entities with filter
filter_params = {
self.unique_entry_name: getattr(versions[0],
self.unique_entry_name)
}
self._assert_filter_result_matches(filter_params,
expected_result=versions[0])
def test_filter_versions_by_release_type(self):
"""Test filtering versions by release type."""
# Create versions for all combinations of release types
versions = []
i = 0
for is_official_release in True, False:
for is_beta_release in True, False:
versions.append(self._create_dummy_version(**{
'is_official_release': is_official_release,
'is_beta_release': is_beta_release,
self.unique_entry_name: self.unique_entries[i]
}))
i += 1
# # Listing all entities should return the correct result length
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, len(versions))
# List each of the entities with the matching filter params
for version in versions:
filter_params = {
'is_official_release': version.is_official_release,
'is_beta_release': version.is_beta_release
}
self._assert_filter_result_matches(filter_params,
expected_result=version)
def test_filter_versions_by_first_seen_date(self):
"""Test filtering versions by first seen date."""
versions = self._create_version_entities()
# Set the first seen date of an entity
versions[0].first_seen_on = Dummy.DATES[2]
versions[0].save()
# Listing all entities should return the correct result length
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, len(versions))
# Expect the single matching result to be returned
filter_params = {'first_seen_after': Dummy.DATES[2]}
self._assert_filter_result_matches(filter_params,
expected_result=versions[0])
# pylint: disable=too-many-ancestors
class RadioVersionTestCase(VersionTestCase):
"""Test the RadioVersion REST endpoint."""
unique_entry_name = 'radio_version'
unique_entries = Dummy.RADIO_VERSIONS
endpoint_url = reverse('hiccup_stats_api_v1_radio_versions')
@staticmethod
def _create_dummy_version(**kwargs):
return Dummy.create_dummy_radio_version(**kwargs)
class VersionDailyTestCase(_VersionTestCase):
"""Test the VersionDaily REST endpoint."""
endpoint_url = reverse('hiccup_stats_api_v1_version_daily')
@staticmethod
def _create_dummy_daily_version(version, **kwargs):
return Dummy.create_dummy_daily_version(version, **kwargs)
def _create_version_entities(self):
versions = [
self._create_dummy_version(
**{self.unique_entry_name: unique_entry}
)
for unique_entry in self.unique_entries
]
versions_daily = [
self._create_dummy_daily_version(version=version)
for version in versions
]
return versions_daily
def test_list_daily_versions_without_authentication(self):
"""Test listing of daily versions without authentication."""
response = self.client.get(self.endpoint_url)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_list_daily_versions_as_device_owner(self):
"""Test listing of daily versions as device owner."""
self._assert_device_owner_has_no_get_access(self.endpoint_url)
def test_list_daily_versions_empty_database(self):
"""Test listing of daily versions on an empty database."""
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, 0)
def test_list_daily_versions(self):
"""Test listing daily versions."""
versions_daily = self._create_version_entities()
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, len(versions_daily))
def test_filter_daily_versions_by_version(self):
"""Test filtering versions by the version they relate to."""
# Create VersionDaily entities
versions = self._create_version_entities()
# Listing all entities should return the correct result length
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, len(versions))
# List entities with filter
param_name = 'version__' + self.unique_entry_name
filter_params = {
param_name: getattr(versions[0].version, self.unique_entry_name)
}
self._assert_filter_result_matches(filter_params,
expected_result=versions[0].version)
def test_filter_daily_versions_by_date(self):
"""Test filtering daily versions by date."""
# Create Version and VersionDaily entities
versions = self._create_version_entities()
# Update the date
versions[0].date = Dummy.DATES[2]
versions[0].save()
# Listing all entities should return the correct result length
response = self.admin.get(self.endpoint_url)
self._assert_result_length_is(response, len(versions))
# Expect the single matching result to be returned
filter_params = {'date': versions[0].date}
self._assert_filter_result_matches(filter_params,
expected_result=versions[0].version)
class RadioVersionDailyTestCase(VersionDailyTestCase):
"""Test the RadioVersionDaily REST endpoint."""
unique_entry_name = 'radio_version'
unique_entries = Dummy.RADIO_VERSIONS
endpoint_url = reverse('hiccup_stats_api_v1_radio_version_daily')
@staticmethod
def _create_dummy_version(**kwargs):
entity = RadioVersion(**Dummy.update_copy(
Dummy.DEFAULT_DUMMY_RADIO_VERSION_VALUES, kwargs))
entity.save()
return entity
@staticmethod
def _create_dummy_daily_version(version, **kwargs):
return Dummy.create_dummy_daily_radio_version(version, **kwargs)