Merge common code of dummy testing classes

Move all shared functionality to crashreports/tests/utils.py and make
the dummy in crashreport_stats/tests/test_rest_endpoints.py inherit from
the crashreports dummy class to prevent code duplication.

Issue: HIC-269
Change-Id: I69ce569c13169ded5857638c466bea8a650f38fd
diff --git a/crashreports/tests/utils.py b/crashreports/tests/utils.py
index 1202fce..9ee316a 100644
--- a/crashreports/tests/utils.py
+++ b/crashreports/tests/utils.py
@@ -1,18 +1,19 @@
 """Utility functions shared by all crashreports tests."""
 
 import os
+import zipfile
+from datetime import date, datetime
 from typing import Optional
 
+import pytz
 from django.contrib.auth.models import User, Group
 from django.urls import reverse
 from rest_framework import status
 from rest_framework.test import APITestCase, APIClient
 
-from crashreports.models import Crashreport
+from crashreports.models import Crashreport, Device, HeartBeat, LogFile
 from hiccup.allauth_adapters import FP_STAFF_GROUP_NAME
 
-DEFAULT_DUMMY_LOG_FILE_DIRECTORY = os.path.join("resources", "test")
-
 
 class InvalidCrashTypeError(BaseException):
     """Invalid crash type encountered.
@@ -40,42 +41,82 @@
 class Dummy:
     """Dummy values for devices, heartbeats and crashreports."""
 
+    # 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_USER_VALUES = {"username": USERNAMES[0]}
+
     DEFAULT_DUMMY_DEVICE_REGISTER_VALUES = {
-        "board_date": "2015-12-15T01:23:45Z",
+        "board_date": datetime(2015, 12, 15, 1, 23, 45, tzinfo=pytz.utc),
         "chipset": "Qualcomm MSM8974PRO-AA",
     }
 
+    DEFAULT_DUMMY_DEVICE_VALUES = DEFAULT_DUMMY_DEVICE_REGISTER_VALUES.copy()
+    DEFAULT_DUMMY_DEVICE_VALUES.update(
+        {"token": "64111c62d521fb4724454ca6dea27e18f93ef56e"}
+    )
+
     DEFAULT_DUMMY_HEARTBEAT_VALUES = {
-        "uuid": None,
         "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": (
-            "Fairphone/FP2/FP2:6.0.1/FP2-gms-18.03.1/FP2-gms-18.03.1:user/"
-            "release-keys"
-        ),
-        "radio_version": "4437.1-FP2-0-08",
-        "date": "2018-03-19T09:58:30.386Z",
+        "build_fingerprint": BUILD_FINGERPRINTS[0],
+        "radio_version": RADIO_VERSIONS[0],
+        "date": datetime(2018, 3, 19, 12, 0, 0, tzinfo=pytz.utc),
     }
 
-    DEFAULT_DUMMY_CRASHREPORTS_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
-    DEFAULT_DUMMY_CRASHREPORTS_VALUES.update(
+    DEFAULT_DUMMY_CRASHREPORT_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
+    DEFAULT_DUMMY_CRASHREPORT_VALUES.update(
         {
             "is_fake_report": 0,
-            "boot_reason": "why?",
+            "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": "test_logfile.zip",
+    }
+
+    DEFAULT_DUMMY_LOG_FILE_NAME = "dmesg.log"
+
     CRASH_TYPE_TO_BOOT_REASON_MAP = {
         "crash": Crashreport.BOOT_REASON_KEYBOARD_POWER_ON,
         "smpl": Crashreport.BOOT_REASON_RTC_ALARM,
         "other": "whatever",
     }
 
+    DEFAULT_DUMMY_LOG_FILE_DIRECTORY = os.path.join("resources", "test")
+
     DEFAULT_DUMMY_LOG_FILE_PATH = os.path.join(
         DEFAULT_DUMMY_LOG_FILE_DIRECTORY, "test_logfile.zip"
     )
@@ -121,7 +162,7 @@
                 keyword arguments already.
         """
         data = Dummy._update_copy(
-            Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES, kwargs
+            Dummy.DEFAULT_DUMMY_CRASHREPORT_VALUES, kwargs
         )
         if report_type and "boot_reason" not in kwargs:
             if report_type not in Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP:
@@ -131,6 +172,111 @@
             )
         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.
+
+        Raises:
+            RuntimeError: If report_type is not a report class type.
+
+        """
+        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)
+
 
 class HiccupCrashreportsAPITestCase(APITestCase):
     """Base class that offers a device registration method."""