Refactor device registration in test cases

Device registration and admin user creation should only happen
where it is needed. Further update the coding style and add
docstrings to match the expectations of the linters.

Issue: HIC-153
Change-Id: Ic4c6d11f7275411ec1bc0164d70c3094c37fe905
diff --git a/crashreports/tests.py b/crashreports/tests.py
index 31a24b5..2267a79 100644
--- a/crashreports/tests.py
+++ b/crashreports/tests.py
@@ -1,17 +1,18 @@
+"""Test the API for crashreports, devices, heartbeats and logfiles."""
 import os
 import tempfile
 
 from django.contrib.auth.models import User
 from django.urls import reverse
-from rest_framework.test import APITestCase
-from rest_framework.test import APIClient
+
 from rest_framework import status
+from rest_framework.test import APIClient, APITestCase
 
 from crashreports.models import Crashreport
 
 
 class InvalidCrashTypeError(BaseException):
-    """Invalid crash type encountered
+    """Invalid crash type encountered.
 
     The valid crash type values (strings) are:
       - 'crash';
@@ -23,11 +24,18 @@
     """
 
     def __init__(self, crash_type):
+        """Initialise the exception using the crash type to build a message.
+
+        Args:
+            crash_type: The invalid crash type.
+        """
         super(InvalidCrashTypeError, self).__init__(
             '{} is not a valid crash type'.format(crash_type))
 
 
-class Dummy(object):
+class Dummy():
+    """Dummy values for devices, heartbeats and crashreports."""
+
     DEFAULT_DUMMY_DEVICE_REGISTER_VALUES = {
         'board_date': '2015-12-15T01:23:45Z',
         'chipset': 'Qualcomm MSM8974PRO-AA',
@@ -62,45 +70,42 @@
 
     @staticmethod
     def _update_copy(original, update):
-        """Merge fields of update into a copy of original"""
+        """Merge fields of update into a copy of original."""
         data = original.copy()
         data.update(update)
         return data
 
     @staticmethod
     def device_register_data(**kwargs):
-        """
-            Return the data required to register a device.
+        """Return the data required to register a device.
 
-            Use the values passed as keyword arguments or default to
-            the ones from `Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES`.
+        Use the values passed as keyword arguments or default to the ones
+        from `Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES`.
         """
         return Dummy._update_copy(
             Dummy.DEFAULT_DUMMY_DEVICE_REGISTER_VALUES, kwargs)
 
     @staticmethod
     def heartbeat_data(**kwargs):
-        """
-            Return the data required to create a heartbeat.
+        """Return the data required to create a heartbeat.
 
-            Use the values passed as keyword arguments or default to
-            the ones from `Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES`.
+        Use the values passed as keyword arguments or default to the ones
+        from `Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES`.
         """
         return Dummy._update_copy(Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs)
 
     @staticmethod
     def crashreport_data(report_type=None, **kwargs):
-        """
-            Return the data required to create a crashreport.
+        """Return the data required to create a crashreport.
 
-            Use the values passed as keyword arguments or default to
-            the ones from `Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES`.
+        Use the values passed as keyword arguments or default to the ones
+        from `Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES`.
 
-            Args:
-                report_type (str, optional): A valid value from
-                    `Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.keys()` that will
-                    define the boot reason if not explicitly defined in the
-                    keyword arguments already.
+        Args:
+            report_type (str, optional): A valid value from
+                `Dummy.CRASH_TYPE_TO_BOOT_REASON_MAP.keys()` that will
+                define the boot reason if not explicitly defined in the
+                keyword arguments already.
         """
         data = Dummy._update_copy(
             Dummy.DEFAULT_DUMMY_CRASHREPORTS_VALUES, kwargs)
@@ -112,151 +117,164 @@
         return data
 
 
-class DeviceTestCase(APITestCase):
-
-    def setUp(self):
-        self.url = reverse("api_v1_register_device")
-
-    def test(self):
-        request = self.client.post(self.url, Dummy.device_register_data())
-        self.assertTrue("token" in request.data)
-        self.assertTrue("uuid" in request.data)
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-
-    def test_create_missing_fields(self):
-        request = self.client.post(self.url)
-        self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST)
-
-    def test_create_missing_board_date(self):
-        data = Dummy.device_register_data()
-        data.pop('board_date')
-        request = self.client.post(self.url, data)
-        self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST)
-
-    def test_create_missing_chipset(self):
-        data = Dummy.device_register_data()
-        data.pop('chipset')
-        request = self.client.post(self.url, data)
-        self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST)
-
-    def test_create_invalid_board_date(self):
-        data = Dummy.device_register_data()
-        data['board_date'] = 'not_a_valid_date'
-        request = self.client.post(self.url, data)
-        self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST)
-
-    def test_create_non_existent_time_board_date(self):
-        """
-        Test the resolution of a naive date-time in which the Europe/Amsterdam daylight saving
-        time transition moved the time "forward".
-        """
-        data = Dummy.device_register_data()
-        # In 2017, the Netherlands changed from CET to CEST on March, 26 at 02:00
-        data['board_date'] = '2017-03-26 02:34:56'
-        request = self.client.post(self.url, data)
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-
-    def test_create_ambiguous_time_board_date(self):
-        """
-        Test the resolution of a naive date-time in which the Europe/Amsterdam daylight saving
-        time transition moved the time "backward".
-        """
-        data = Dummy.device_register_data()
-        # In 2017, the Netherlands changed from CEST to CET on October, 29 at 03:00
-        data['board_date'] = '2017-10-29 02:34:56'
-        request = self.client.post(self.url, data)
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-
-
 class DeviceRegisterAPITestCase(APITestCase):
-    """Base class that offers device registration as well as base users
-
-    Attributes:
-        number_of_devices_created (int): The number of devices created in the
-            database.
-    """
+    """Base class that offers a device registration method."""
 
     REGISTER_DEVICE_URL = "api_v1_register_device"
 
     def setUp(self):
-        self.number_of_devices_created = 0
+        """Create an admin user for accessing the API.
 
-        _, response_data = self._register_device()
-        self.uuid = response_data['uuid']
-        self.token = response_data['token']
-        self.user = APIClient()
-        self.user.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
-
-        _, response_data = self._register_device()
-        self.other_uuid = response_data['uuid']
-        self.other_token = response_data['token']
-        self.other_user = APIClient()
-        self.other_user.credentials(
-            HTTP_AUTHORIZATION='Token ' + self.other_token)
-
-        self.noauth_client = APIClient()
-
-        self.admin_user = User.objects.create_superuser(
+        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')
         self.admin = APIClient()
-        self.admin.force_authenticate(self.admin_user)
+        self.admin.force_authenticate(admin_user)
 
     def _register_device(self, **kwargs):
-        """Register a new device
+        """Register a new device.
 
         Arguments:
             **kwargs: The data to pass the dummy data creation
                 method `Dummy.device_register_data`.
         Returns:
-            (dict(str, str), dict(str, str)): The tuple composed of the device
-            data that was used for the registration and the data returned by
-            the server in return.
+            (UUID, APIClient, str): The uuid of the new device as well as an
+            authentication token and the associated user with credentials.
+
         """
         data = Dummy.device_register_data(**kwargs)
         response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
         self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.number_of_devices_created += 1
 
-        return (data, response.data)
+        uuid = response.data['uuid']
+        token = response.data['token']
+        user = APIClient()
+        user.credentials(HTTP_AUTHORIZATION='Token ' + token)
+
+        return uuid, user, token
+
+
+class DeviceTestCase(DeviceRegisterAPITestCase):
+    """Test cases for registering devices."""
+
+    def test_register(self):
+        """Test registration of devices."""
+        response = self.client.post(reverse(self.REGISTER_DEVICE_URL),
+                                    Dummy.device_register_data())
+        self.assertTrue("token" in response.data)
+        self.assertTrue("uuid" in response.data)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+    def test_create_missing_fields(self):
+        """Test registration with missing fields."""
+        response = self.client.post(reverse(self.REGISTER_DEVICE_URL))
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_missing_board_date(self):
+        """Test registration with missing board date."""
+        data = Dummy.device_register_data()
+        data.pop('board_date')
+        response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_missing_chipset(self):
+        """Test registration with missing chipset."""
+        data = Dummy.device_register_data()
+        data.pop('chipset')
+        response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_invalid_board_date(self):
+        """Test registration with invalid board date."""
+        data = Dummy.device_register_data()
+        data['board_date'] = 'not_a_valid_date'
+        response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_non_existent_time_board_date(self):
+        """Test registration with non existing time.
+
+        Test the resolution of a naive date-time in which the
+        Europe/Amsterdam daylight saving time transition moved the time
+        "forward". The server should not crash when receiving a naive
+        date-time which does not exist in the server timezone or locale.
+        """
+        data = Dummy.device_register_data()
+        # In 2017, the Netherlands changed from CET to CEST on March,
+        # 26 at 02:00
+        data['board_date'] = '2017-03-26 02:34:56'
+        response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+    def test_create_ambiguous_time_board_date(self):
+        """Test registration with ambiguous time.
+
+        Test the resolution of a naive date-time in which the
+        Europe/Amsterdam daylight saving time transition moved the time
+        "backward". The server should not crash when receiving a naive
+        date-time that can belong to multiple timezones.
+        """
+        data = Dummy.device_register_data()
+        # In 2017, the Netherlands changed from CEST to CET on October,
+        # 29 at 03:00
+        data['board_date'] = '2017-10-29 02:34:56'
+        response = self.client.post(reverse(self.REGISTER_DEVICE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
 
 
 class ListDevicesTestCase(DeviceRegisterAPITestCase):
+    """Test cases for listing and deleting devices."""
 
     LIST_CREATE_URL = "api_v1_list_devices"
     RETRIEVE_URL = "api_v1_retrieve_device"
 
     def test_device_list(self):
-        request = self.admin.get(reverse(self.LIST_CREATE_URL), {})
-        self.assertIsNot(request.data['results'][1]['uuid'], '')
-        self.assertEqual(
-            len(request.data['results']), self.number_of_devices_created)
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
+        """Test registration of 2 devices."""
+        number_of_devices = 2
+        uuids = [
+            str(self._register_device()[0])
+            for _ in range(number_of_devices)
+        ]
+
+        response = self.admin.get(reverse(self.LIST_CREATE_URL), {})
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(len(response.data['results']), number_of_devices)
+        for result in response.data['results']:
+            self.assertIn(result['uuid'], uuids)
 
     def test_device_list_unauth(self):
-        request = self.client.get(reverse(self.LIST_CREATE_URL), {})
-        self.assertEqual(request.status_code, status.HTTP_401_UNAUTHORIZED)
+        """Test listing devices without authentication."""
+        response = self.client.get(reverse(self.LIST_CREATE_URL), {})
+        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
 
     def test_retrieve_device_auth(self):
-        request = self.admin.get(
-            reverse(self.RETRIEVE_URL, args=[self.uuid]), {})
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-        self.assertEqual(request.data['uuid'], str(self.uuid))
-        self.assertEqual(request.data['token'], self.token)
+        """Test retrieval of devices as admin user."""
+        uuid, _, token = self._register_device()
+        response = self.admin.get(reverse(self.RETRIEVE_URL, args=[uuid]), {})
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data['uuid'], str(uuid))
+        self.assertEqual(response.data['token'], token)
 
     def test_retrieve_device_unauth(self):
-        request = self.client.get(
-            reverse(self.RETRIEVE_URL, args=[self.uuid]), {})
-        self.assertEqual(request.status_code, status.HTTP_401_UNAUTHORIZED)
+        """Test retrieval of devices without authentication."""
+        uuid, _, _ = self._register_device()
+        response = self.client.get(reverse(self.RETRIEVE_URL, args=[uuid]), {})
+        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
 
     def test_delete_device_auth(self):
-        url = reverse(self.RETRIEVE_URL, args=[self.other_uuid])
-        request = self.admin.delete(url, {})
-        self.assertEqual(request.status_code, status.HTTP_204_NO_CONTENT)
-        request = self.admin.delete(url, {})
-        self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND)
+        """Test deletion of devices as admin user."""
+        uuid, _, _ = self._register_device()
+        url = reverse(self.RETRIEVE_URL, args=[uuid])
+        response = self.admin.delete(url, {})
+        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+        response = self.admin.delete(url, {})
+        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 
 
 class HeartbeatListTestCase(DeviceRegisterAPITestCase):
+    """Test cases for heartbeats."""
 
     LIST_CREATE_URL = "api_v1_heartbeats"
     RETRIEVE_URL = "api_v1_heartbeat"
@@ -274,156 +292,189 @@
 
     def _retrieve_single(self, user):
         count = 5
-        requests = self._post_multiple(self.admin, self.data, count)
-        self.assertEqual(len(requests), count)
-        self.assertEqual(requests[0].status_code, status.HTTP_201_CREATED)
-        url = reverse(self.RETRIEVE_URL, args=[requests[0].data['id']])
+        response = self._post_multiple(self.admin, self.data, count)
+        self.assertEqual(len(response), count)
+        self.assertEqual(response[0].status_code, status.HTTP_201_CREATED)
+        url = reverse(self.RETRIEVE_URL, args=[response[0].data['id']])
         request = user.get(url)
         return request.status_code
 
     def _retrieve_single_by_device(self, user):
         count = 5
-        requests = self._post_multiple(self.user, self.data, count)
-        self.assertEqual(len(requests), count)
-        self.assertEqual(requests[0].status_code, status.HTTP_201_CREATED)
+        response = self._post_multiple(self.user, self.data, count)
+        self.assertEqual(len(response), count)
+        self.assertEqual(response[0].status_code, status.HTTP_201_CREATED)
         url = reverse(self.RETRIEVE_BY_UUID_URL, args=[
-            self.uuid, requests[0].data['device_local_id']])
+            self.uuid, response[0].data['device_local_id']])
         request = user.get(url)
         return request.status_code
 
     def setUp(self):
-        super(HeartbeatListTestCase, self).setUp()
+        """Set up a device and some data."""
+        super().setUp()
+        self.uuid, self.user, self.token = self._register_device()
         self.data = self._create_dummy_data(uuid=self.uuid)
 
     def test_create_no_auth(self):
-        request = self.noauth_client.post(
+        """Test creation without authentication."""
+        noauth_client = APIClient()
+        response = noauth_client.post(
             reverse(self.LIST_CREATE_URL), self.data)
-        self.assertEqual(request.status_code, status.HTTP_401_UNAUTHORIZED)
+        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
 
     def test_create_as_admin(self):
-        request = self.admin.post(reverse(self.LIST_CREATE_URL), self.data)
-        self.assertEqual(request.status_code, status.HTTP_201_CREATED)
-        self.assertTrue(request.data['id'] > 0)
+        """Test creation as admin."""
+        response = self.admin.post(reverse(self.LIST_CREATE_URL), self.data)
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertTrue(response.data['id'] > 0)
 
     def test_create_as_admin_not_existing_device(self):
-        request = self.admin.post(
+        """Test creation of heartbeat on non-existing device."""
+        response = self.admin.post(
             reverse(self.LIST_CREATE_URL), self._create_dummy_data())
-        self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND)
+        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 
     def test_create_as_uuid_owner(self):
-        request = self.user.post(
+        """Test creation as owner."""
+        response = self.user.post(
             reverse(self.LIST_CREATE_URL),
             self._create_dummy_data(uuid=self.uuid))
-        self.assertEqual(request.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(request.data['id'], -1)
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.data['id'], -1)
 
     def test_create_as_uuid_not_owner(self):
-        request = self.user.post(
+        """Test creation as non-owner."""
+        uuid, _, _ = self._register_device()
+        response = self.user.post(
             reverse(self.LIST_CREATE_URL),
-            self._create_dummy_data(uuid=self.other_uuid))
-        self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN)
+            self._create_dummy_data(uuid=uuid))
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 
     def test_list(self):
+        """Test listing of heartbeats."""
         count = 5
         self._post_multiple(self.user, self.data, count)
-        request = self.admin.get(reverse(self.LIST_CREATE_URL))
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-        self.assertEqual(len(request.data['results']), count)
+        response = self.admin.get(reverse(self.LIST_CREATE_URL))
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(len(response.data['results']), count)
 
     def test_retrieve_single_admin(self):
+        """Test retrieval as admin."""
         self.assertEqual(
             self._retrieve_single(self.admin), status.HTTP_200_OK)
 
     def test_retrieve_single_device_owner(self):
+        """Test retrieval as device owner."""
         self.assertEqual(
             self._retrieve_single(self.user), status.HTTP_403_FORBIDDEN)
 
     def test_retrieve_single_noauth(self):
+        """Test retrieval without authentication."""
+        noauth_client = APIClient()
         self.assertEqual(
-            self._retrieve_single(self.noauth_client),
+            self._retrieve_single(noauth_client),
             status.HTTP_401_UNAUTHORIZED)
 
     def test_retrieve_single_by_device_admin(self):
+        """Test retrieval by device as admin."""
         self.assertEqual(
             self._retrieve_single_by_device(self.admin), status.HTTP_200_OK)
 
     def test_retrieve_single_by_device_device_owner(self):
+        """Test retrieval by device as owner."""
         self.assertEqual(
             self._retrieve_single_by_device(self.user),
             status.HTTP_403_FORBIDDEN)
 
     def test_retrieve_single_by_device_noauth(self):
+        """Test retrieval by device without authentication."""
+        noauth_client = APIClient()
         self.assertEqual(
-            self._retrieve_single_by_device(self.noauth_client),
+            self._retrieve_single_by_device(noauth_client),
             status.HTTP_401_UNAUTHORIZED)
 
     def test_list_by_uuid(self):
+        """Test listing of devices by UUID."""
         count = 5
+        uuid, _, _ = self._register_device()
         self._post_multiple(self.user, self.data, count)
         self._post_multiple(
-            self.admin, self._create_dummy_data(uuid=self.other_uuid), count)
+            self.admin, self._create_dummy_data(uuid=uuid), count)
         url = reverse(self.LIST_CREATE_BY_UUID_URL, args=[self.uuid])
-        request = self.admin.get(url)
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-        self.assertEqual(len(request.data['results']), count)
+        response = self.admin.get(url)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(len(response.data['results']), count)
 
     def test_list_noauth(self):
+        """Test listing of devices without authentication."""
         count = 5
+        noauth_client = APIClient()
         self._post_multiple(self.user, self.data, count)
-        request = self.noauth_client.get(reverse(self.LIST_CREATE_URL))
-        self.assertEqual(request.status_code, status.HTTP_401_UNAUTHORIZED)
+        response = noauth_client.get(reverse(self.LIST_CREATE_URL))
+        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
 
     def test_list_device_owner(self):
+        """Test listing as device owner."""
         count = 5
         self._post_multiple(self.user, self.data, count)
-        request = self.user.get(reverse(self.LIST_CREATE_URL))
-        self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN)
+        response = self.user.get(reverse(self.LIST_CREATE_URL))
+        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 
     def test_no_radio_version(self):
+        """Test creation and retrieval without radio version."""
         data = self._create_dummy_data(uuid=self.uuid)
         data.pop('radio_version')
-        request = self.user.post(reverse(self.LIST_CREATE_URL), data)
-        self.assertEqual(request.status_code, status.HTTP_201_CREATED)
+        response = self.user.post(reverse(self.LIST_CREATE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
         url = reverse(self.LIST_CREATE_BY_UUID_URL, args=[self.uuid])
-        request = self.admin.get(url)
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-        self.assertEqual(len(request.data['results']), 1)
-        self.assertIsNone(request.data['results'][0]['radio_version'])
+        response = self.admin.get(url)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(len(response.data['results']), 1)
+        self.assertIsNone(response.data['results'][0]['radio_version'])
 
     def test_radio_version_field(self):
-        request = self.user.post(reverse(self.LIST_CREATE_URL), self.data)
-        self.assertEqual(request.status_code, status.HTTP_201_CREATED)
+        """Test retrieval of radio version field."""
+        response = self.user.post(reverse(self.LIST_CREATE_URL), self.data)
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
         url = reverse(self.LIST_CREATE_BY_UUID_URL, args=[self.uuid])
-        request = self.admin.get(url)
-        self.assertEqual(request.status_code, status.HTTP_200_OK)
-        self.assertEqual(len(request.data['results']), 1)
-        self.assertEqual(request.data['results'][0]['radio_version'],
-                self.data['radio_version'])
+        response = self.admin.get(url)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(len(response.data['results']), 1)
+        self.assertEqual(response.data['results'][0]['radio_version'],
+                         self.data['radio_version'])
 
     def test_send_non_existent_time(self):
-        """
-        Test the resolution of a naive date-time in which the Europe/Amsterdam daylight saving
-        time transition moved the time "forward".
+        """Test sending of heartbeat with non existent time.
+
+        Test the resolution of a naive date-time in which the
+        Europe/Amsterdam daylight saving time transition moved the time
+        "forward".
         """
         data = self._create_dummy_data(uuid=self.uuid)
-        # In 2017, the Netherlands changed from CET to CEST on March, 26 at 02:00
+        # In 2017, the Netherlands changed from CET to CEST on March,
+        # 26 at 02:00
         data['date'] = '2017-03-26 02:34:56'
-        request = self.user.post(reverse(self.LIST_CREATE_URL), data)
-        self.assertEqual(request.status_code, status.HTTP_201_CREATED)
+        response = self.user.post(reverse(self.LIST_CREATE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
 
     def test_send_ambiguous_time(self):
-        """
-        Test the resolution of a naive date-time in which the Europe/Amsterdam daylight saving
-        time transition moved the time "backward".
+        """Test sending of heartbeat with ambiguous time.
+
+        Test the resolution of a naive date-time in which the
+        Europe/Amsterdam daylight saving time transition moved the time
+        "backward".
         """
         data = self._create_dummy_data(uuid=self.uuid)
-        # In 2017, the Netherlands changed from CEST to CET on October, 29 at 03:00
+        # In 2017, the Netherlands changed from CEST to CET on October,
+        # 29 at 03:00
         data['date'] = '2017-10-29 02:34:56'
-        request = self.user.post(reverse(self.LIST_CREATE_URL), data)
-        self.assertEqual(request.status_code, status.HTTP_201_CREATED)
+        response = self.user.post(reverse(self.LIST_CREATE_URL), data)
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
 
 
+# pylint: disable=too-many-ancestors
 class CrashreportListTestCase(HeartbeatListTestCase):
+    """Test cases for crash reports."""
 
     LIST_CREATE_URL = "api_v1_crashreports"
     RETRIEVE_URL = "api_v1_crashreport"
@@ -436,25 +487,50 @@
 
 
 class LogfileUploadTest(DeviceRegisterAPITestCase):
+    """Test cases for upload of log files."""
 
     LIST_CREATE_URL = "api_v1_crashreports"
     PUT_LOGFILE_URL = "api_v1_putlogfile_for_device_id"
 
-    def setUp(self):
-        super(LogfileUploadTest, self).setUp()
+    def _upload_crashreport(self, user, uuid):
+        """
+        Upload dummy crashreport data.
 
-        url = reverse(self.LIST_CREATE_URL)
-        for uuid in [self.uuid, self.other_uuid]:
-            data = Dummy.crashreport_data(uuid=uuid)
-            for _ in range(2):
-                self.user.post(url, data)
+        Args:
+            user: The user which should be used for uploading the report
+            uuid: The uuid of the device to which the report should be uploaded
 
-    def test_Logfile_upload_as_admin(self):
-        f = tempfile.NamedTemporaryFile('w+', suffix=".log", delete=True)
-        f.write(u"blihblahblub")
-        request = self.admin.post(
+        Returns: The local id of the device for which the report was uploaded.
+
+        """
+        data = Dummy.crashreport_data(uuid=uuid)
+        response = user.post(reverse(self.LIST_CREATE_URL), data)
+        self.assertEqual(status.HTTP_201_CREATED, response.status_code)
+        self.assertTrue('device_local_id' in response.data)
+        device_local_id = response.data['device_local_id']
+
+        return device_local_id
+
+    def _test_logfile_upload(self, user, uuid):
+        # Upload crashreport
+        device_local_id = self._upload_crashreport(user, uuid)
+
+        # Upload a logfile for the crashreport
+        logfile = tempfile.NamedTemporaryFile('w+', suffix=".log", delete=True)
+        logfile.write(u"blihblahblub")
+        response = user.post(
             reverse(self.PUT_LOGFILE_URL, args=[
-                self.uuid, 1, os.path.basename(f.name)
+                uuid, device_local_id, os.path.basename(logfile.name)
             ]),
-            {'file': f}, format="multipart")
-        self.assertEqual(status.HTTP_201_CREATED, request.status_code)
+            {'file': logfile}, format="multipart")
+        self.assertEqual(status.HTTP_201_CREATED, response.status_code)
+
+    def test_logfile_upload_as_user(self):
+        """Test upload of logfiles as device owner."""
+        uuid, user, _ = self._register_device()
+        self._test_logfile_upload(user, uuid)
+
+    def test_logfile_upload_as_admin(self):
+        """Test upload of logfiles as admin user."""
+        uuid, _, _ = self._register_device()
+        self._test_logfile_upload(self.admin, uuid)