blob: 0fd9fc94826857a9a42fe81815e78c50eaad7cee [file] [log] [blame]
"""Test crashreport_stats models and the 'stats' command."""
from io import StringIO
from datetime import datetime, date, timedelta
import pytz
from django.core.management import call_command
from django.test import TestCase
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,
StatsMetadata,
)
from crashreports.models import User, Device, Crashreport, HeartBeat
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_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": "testuser"}
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, 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",
}
)
@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_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 _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)
class StatsCommandVersionsTestCase(TestCase):
"""Test the generation of Version stats with the stats command."""
# The class of the version type to be tested
version_class = Version
# 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
def _create_reports(
self, report_type, unique_entry_name, device, number, **kwargs
):
# Create reports with distinct timestamps
now = datetime.now(pytz.utc)
for i in range(number):
report_date = now - timedelta(milliseconds=i)
report_attributes = {
self.unique_entry_name: unique_entry_name,
"device": device,
"date": report_date,
}
report_attributes.update(**kwargs)
Dummy.create_dummy_report(report_type, **report_attributes)
def test_stats_calculation(self):
"""Test generation of a Version instance."""
user = Dummy.create_dummy_user()
device = Dummy.create_dummy_device(user=user)
heartbeat = Dummy.create_dummy_report(HeartBeat, device=device)
# Expect that we do not have the Version before updating the stats
get_params = {
self.unique_entry_name: getattr(heartbeat, self.unique_entry_name)
}
self.assertRaises(
self.version_class.DoesNotExist,
self.version_class.objects.get,
**get_params
)
# Run the command to update the database
call_command("stats", "update")
# Assume that a corresponding Version instance has been created
version = self.version_class.objects.get(**get_params)
self.assertIsNotNone(version)
def _assert_older_report_updates_version_date(self, report_type):
"""Validate that older reports sent later affect the version date."""
user = Dummy.create_dummy_user()
device = Dummy.create_dummy_device(user=user)
report = Dummy.create_dummy_report(report_type, device=device)
# Run the command to update the database
call_command("stats", "update")
get_params = {
self.unique_entry_name: getattr(report, self.unique_entry_name)
}
version = self.version_class.objects.get(**get_params)
self.assertEqual(report.date.date(), version.first_seen_on)
# Create a new report from an earlier point in time
report_time_2 = report.date - timedelta(weeks=1)
Dummy.create_dummy_report(
report_type, device=device, date=report_time_2
)
# Run the command to update the database
call_command("stats", "update")
# Get the same version object from before
version = self.version_class.objects.get(**get_params)
# Validate that the date matches the report recently sent
self.assertEqual(report_time_2.date(), version.first_seen_on)
def test_older_heartbeat_updates_version_date(self):
"""Validate updating version date with older heartbeats."""
self._assert_older_report_updates_version_date(HeartBeat)
def test_older_crash_report_updates_version_date(self):
"""Validate updating version date with older crash reports."""
self._assert_older_report_updates_version_date(Crashreport)
def test_entries_are_unique(self):
"""Validate the entries' unicity and value."""
# Create some reports
user = Dummy.create_dummy_user()
device = Dummy.create_dummy_device(user=user)
for unique_entry in self.unique_entries:
self._create_reports(HeartBeat, unique_entry, device, 10)
# Run the command to update the database
call_command("stats", "update")
# Check whether the correct amount of distinct versions have been
# created
versions = self.version_class.objects.all()
for version in versions:
self.assertIn(
getattr(version, self.unique_entry_name), self.unique_entries
)
self.assertEqual(len(versions), len(self.unique_entries))
def _assert_counter_distribution_is_correct(
self, report_type, numbers, counter_attribute_name, **kwargs
):
"""Validate a counter distribution in the database."""
if len(numbers) != len(self.unique_entries):
raise ValueError(
"The length of the numbers list must match the "
"length of self.unique_entries in the test class"
"({} != {})".format(len(numbers), len(self.unique_entries))
)
# Create some reports
user = Dummy.create_dummy_user()
device = Dummy.create_dummy_device(user=user)
for unique_entry, num in zip(self.unique_entries, numbers):
self._create_reports(
report_type, unique_entry, device, num, **kwargs
)
# Run the command to update the database
call_command("stats", "update")
# Check whether the numbers of reports match
for version in self.version_class.objects.all():
unique_entry_name = getattr(version, self.unique_entry_name)
num = numbers[self.unique_entries.index(unique_entry_name)]
self.assertEqual(num, getattr(version, counter_attribute_name))
def test_heartbeats_counter(self):
"""Test the calculation of the heartbeats counter."""
numbers = [10, 7, 8, 5]
counter_attribute_name = "heartbeats"
self._assert_counter_distribution_is_correct(
HeartBeat, numbers, counter_attribute_name
)
def test_crash_reports_counter(self):
"""Test the calculation of the crashreports counter."""
numbers = [2, 5, 0, 3]
counter_attribute_name = "prob_crashes"
boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_UNKOWN}
self._assert_counter_distribution_is_correct(
Crashreport, numbers, counter_attribute_name, **boot_reason_param
)
def test_smpl_reports_counter(self):
"""Test the calculation of the smpl reports counter."""
numbers = [1, 3, 4, 0]
counter_attribute_name = "smpl"
boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_RTC_ALARM}
self._assert_counter_distribution_is_correct(
Crashreport, numbers, counter_attribute_name, **boot_reason_param
)
def test_other_reports_counter(self):
"""Test the calculation of the other reports counter."""
numbers = [0, 2, 1, 2]
counter_attribute_name = "other"
boot_reason_param = {"boot_reason": "random boot reason"}
self._assert_counter_distribution_is_correct(
Crashreport, numbers, counter_attribute_name, **boot_reason_param
)
def _assert_duplicates_are_ignored(
self, report_type, device, counter_attribute_name, **kwargs
):
"""Validate that reports with duplicate timestamps are ignored."""
# Create a report
report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
# Create a second report with the same timestamp
Dummy.create_dummy_report(
report_type, device=device, date=report.date, **kwargs
)
# Run the command to update the database
call_command("stats", "update")
# Get the corresponding version instance from the database
get_params = {
self.unique_entry_name: getattr(report, self.unique_entry_name)
}
version = self.version_class.objects.get(**get_params)
# Assert that the report with the duplicate timestamp is not
# counted, i.e. only 1 report is counted.
self.assertEqual(getattr(version, counter_attribute_name), 1)
def test_heartbeat_duplicates_are_ignored(self):
"""Validate that heartbeat duplicates are ignored."""
counter_attribute_name = "heartbeats"
device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
self._assert_duplicates_are_ignored(
HeartBeat, device, counter_attribute_name
)
def test_crash_report_duplicates_are_ignored(self):
"""Validate that crash report duplicates are ignored."""
counter_attribute_name = "prob_crashes"
device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
for i, boot_reason in enumerate(Crashreport.CRASH_BOOT_REASONS):
params = {
"boot_reason": boot_reason,
self.unique_entry_name: self.unique_entries[i],
}
self._assert_duplicates_are_ignored(
Crashreport, device, counter_attribute_name, **params
)
def test_smpl_report_duplicates_are_ignored(self):
"""Validate that smpl report duplicates are ignored."""
counter_attribute_name = "smpl"
device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
for i, boot_reason in enumerate(Crashreport.SMPL_BOOT_REASONS):
params = {
"boot_reason": boot_reason,
self.unique_entry_name: self.unique_entries[i],
}
self._assert_duplicates_are_ignored(
Crashreport, device, counter_attribute_name, **params
)
def test_other_report_duplicates_are_ignored(self):
"""Validate that other report duplicates are ignored."""
counter_attribute_name = "other"
params = {"boot_reason": "random boot reason"}
device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
self._assert_duplicates_are_ignored(
Crashreport, device, counter_attribute_name, **params
)
# pylint: disable=too-many-ancestors
class StatsCommandRadioVersionsTestCase(StatsCommandVersionsTestCase):
"""Test the generation of RadioVersion stats with the stats command."""
version_class = RadioVersion
unique_entry_name = "radio_version"
unique_entries = Dummy.RADIO_VERSIONS
class CommandDebugOutputTestCase(TestCase):
"""Test the reset and update commands debug output."""
# Additional positional arguments to pass to the commands
_CMD_ARGS = ["--no-color", "-v 2"]
# The stats models
_STATS_MODELS = [Version, VersionDaily, RadioVersion, RadioVersionDaily]
# The models that will generate an output
_ALL_MODELS = _STATS_MODELS + [StatsMetadata]
_COUNTER_NAMES = ["heartbeats", "crashes", "smpl", "other"]
_COUNTER_ACTIONS = ["created", "updated"]
def _assert_command_output_matches(self, command, number, facts, models):
"""Validate the debug output of a command.
The debug output is matched against the facts and models given in
the parameters.
"""
buffer = StringIO()
call_command("stats", command, *self._CMD_ARGS, stdout=buffer)
output = buffer.getvalue().splitlines()
expected_output = "{number} {model} {fact}"
for model in models:
for fact in facts:
self.assertIn(
expected_output.format(
number=number, model=model.__name__, fact=fact
),
output,
)
def test_reset_command_on_empty_db(self):
"""Test the reset command on an empty database.
The reset command should yield nothing on an empty database.
"""
self._assert_command_output_matches(
"reset", 0, ["deleted"], self._ALL_MODELS
)
def test_update_command_on_empty_db(self):
"""Test the update command on an empty database.
The update command should yield nothing on an empty database.
"""
pattern = "{action} for counter {counter}"
facts = [
pattern.format(action=counter_action, counter=counter_name)
for counter_action in self._COUNTER_ACTIONS
for counter_name in self._COUNTER_NAMES
]
self._assert_command_output_matches(
"update", 0, facts, self._STATS_MODELS
)
def test_reset_command_deletion_of_instances(self):
"""Test the deletion of stats model instances with the reset command.
This test validates that model instances get deleted when the
reset command is called on a database that only contains a single
model instance for each class.
"""
# Create dummy version instances
version = Dummy.create_dummy_version()
radio_version = Dummy.create_dummy_radio_version()
Dummy.create_dummy_daily_version(version)
Dummy.create_dummy_daily_radio_version(radio_version)
Dummy.create_dummy_stats_metadata()
# We expect that the model instances get deleted
self._assert_command_output_matches(
"reset", 1, ["deleted"], self._ALL_MODELS
)