Backup module: added some unittests; minor doc updates

- added unittests
- introduced function backups.get_location_path()
- minor docstring / comment and naming updates

Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Michael Pimmer 2018-09-16 17:49:42 +00:00 committed by James Valleroy
parent 7da361bbca
commit d8fc2057d4
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 69 additions and 18 deletions

View File

@ -18,7 +18,6 @@
FreedomBox app to manage backup archives. FreedomBox app to manage backup archives.
""" """
import errno
import json import json
import os import os
@ -26,6 +25,7 @@ from django.utils.text import get_valid_filename
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from plinth import actions from plinth import actions
from plinth.errors import PlinthError
from plinth.menu import main_menu from plinth.menu import main_menu
from plinth.modules import storage from plinth.modules import storage
@ -127,6 +127,16 @@ def get_export_locations():
return locations return locations
def get_location_path(label, locations=None):
"""Returns the location path given a disk label"""
if locations is None:
locations = get_export_locations()
for (location_path, location_label) in locations:
if location_label == label:
return location_path
raise PlinthError("Could not find path of location %s" % label)
def get_export_files(): def get_export_files():
"""Return a dict of exported backup archives found in storage locations.""" """Return a dict of exported backup archives found in storage locations."""
locations = get_export_locations() locations = get_export_locations()
@ -145,14 +155,8 @@ def get_archive_path(location, archive_name):
def find_exported_archive(disk_label, archive_name): def find_exported_archive(disk_label, archive_name):
"""Return the full path for the exported archive file.""" """Return the full path for the exported archive file."""
locations = get_export_locations() location_path = get_location_path(disk_label)
for (location_path, location_name) in locations: return get_archive_path(location_path, archive_name)
if location_name == disk_label:
return get_archive_path(location_path, archive_name)
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
archive_name)
def get_export_apps(filename): def get_export_apps(filename):

View File

@ -26,7 +26,7 @@ from django.core.validators import FileExtensionValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from . import backups as backups_api from . import backups as backups_api
from . import get_export_locations, get_archive_path from . import get_export_locations, get_archive_path, get_location_path
class CreateArchiveForm(forms.Form): class CreateArchiveForm(forms.Form):
@ -93,16 +93,21 @@ class UploadForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialize the form with location choices.""" """Initialize the form with location choices."""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# TODO: write a test that assures the format of get_export_locations locations = get_export_locations()
self.fields['location'].choices = get_export_locations() # users should only be able to select a location name -- don't
# provide paths as a form input for security reasons
location_labels = [(location[1], location[1]) for location in locations]
self.fields['location'].choices = location_labels
def clean(self): def clean(self):
"""Check that the uploaded file does not exist""" """Check that the uploaded file does not yet exist."""
cleaned_data = super().clean() cleaned_data = super().clean()
file = cleaned_data.get('file') file = cleaned_data.get('file')
location = cleaned_data.get('location') location_label = cleaned_data.get('location')
location_path = get_location_path(location_label)
# if other errors occured before, 'file' won't be in cleaned_data
if (file and file.name): if (file and file.name):
filepath = get_archive_path(location, file.name) filepath = get_archive_path(location_path, file.name)
if os.path.exists(filepath): if os.path.exists(filepath):
raise forms.ValidationError("File %s already exists" % file.name) raise forms.ValidationError("File %s already exists" % file.name)
else: else:

View File

@ -19,13 +19,18 @@ Tests for backups module.
""" """
import collections import collections
from django.core.files.uploadedfile import SimpleUploadedFile
import unittest import unittest
from unittest.mock import call, patch, MagicMock from unittest.mock import call, patch, MagicMock
from plinth.errors import PlinthError
from plinth.module_loader import load_modules from plinth.module_loader import load_modules
from ..backups import validate, Packet, backup_apps, restore_apps, \ from ..backups import validate, Packet, backup_apps, restore_apps, \
get_all_apps_for_backup, _get_apps_in_order, _get_manifests, \ get_all_apps_for_backup, _get_apps_in_order, _get_manifests, \
_lockdown_apps, _shutdown_services, _restore_services _lockdown_apps, _shutdown_services, _restore_services
from .. import get_export_locations, get_location_path
from ..forms import UploadForm
def _get_test_manifest(name): def _get_test_manifest(name):
@ -46,8 +51,8 @@ def _get_test_manifest(name):
}) })
class TestBackups(unittest.TestCase): class TestBackupProcesses(unittest.TestCase):
"""Test cases for backups module.""" """Test cases for backup processes"""
def test_packet_process_manifests(self): def test_packet_process_manifests(self):
"""Test that directories/files are collected from manifests.""" """Test that directories/files are collected from manifests."""
@ -83,6 +88,12 @@ class TestBackups(unittest.TestCase):
assert isinstance(apps, list) assert isinstance(apps, list)
# apps may be empty, if no apps supporting backup are installed. # apps may be empty, if no apps supporting backup are installed.
def test_export_locations(self):
"""Check get_export_locations returns a list of tuples of length 2."""
locations = get_export_locations()
assert(len(locations))
assert(len(locations[0]) == 2)
def test__get_apps_in_order(self): def test__get_apps_in_order(self):
"""Test that apps are listed in correct dependency order.""" """Test that apps are listed in correct dependency order."""
load_modules() load_modules()
@ -148,3 +159,34 @@ class TestBackups(unittest.TestCase):
'app_name': 'b', 'app': None, 'was_running': False} 'app_name': 'b', 'app': None, 'was_running': False}
_restore_services(original_state) _restore_services(original_state)
run.assert_called_once_with('service', ['start', 'a']) run.assert_called_once_with('service', ['start', 'a'])
class TestBackupModule(unittest.TestCase):
"""Tests of the backups django module, like views or forms."""
def test_get_location_path(self):
"""Test the 'get_location_path' method"""
locations = [('/var/www', 'dummy location'), ('/etc', 'dangerous')]
location = get_location_path('dummy location', locations)
self.assertEquals(location, locations[0][0])
# verify that an unknown location raises an error
with self.assertRaises(PlinthError):
get_location_path('unknown location', locations)
def test_file_upload(self):
locations = get_export_locations()
location_name = locations[0][1]
post_data = {'location': location_name}
# posting a video should fail
video_file = SimpleUploadedFile("video.mp4", b"file_content",
content_type="video/mp4")
form = UploadForm(post_data, {'file': video_file})
self.assertFalse(form.is_valid())
# posting an archive file should work
archive_file = SimpleUploadedFile("backup.tar.gz", b"file_content",
content_type="application/gzip")
form = UploadForm(post_data, {'file': archive_file})
form.is_valid()
self.assertTrue(form.is_valid())

View File

@ -138,7 +138,7 @@ class UploadArchiveView(SuccessMessageMixin, FormView):
prefix = 'backups' prefix = 'backups'
template_name = 'backups_upload.html' template_name = 'backups_upload.html'
success_url = reverse_lazy('backups:index') success_url = reverse_lazy('backups:index')
success_message = _('Archive uploaded.') success_message = _('Backup file uploaded.')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Return additional context for rendering the template.""" """Return additional context for rendering the template."""