mirror of https://github.com/borgbase/vorta
Unit test improvements and coverage increase. By @bigtedde (#1787)
This commit is contained in:
parent
c807f93faf
commit
60f9fc27b4
|
@ -325,7 +325,7 @@ class VortaApp(QtSingleApplication):
|
||||||
# No fail
|
# No fail
|
||||||
logger.warning('VortaApp.check_failed_response was called with returncode 0')
|
logger.warning('VortaApp.check_failed_response was called with returncode 0')
|
||||||
elif returncode == 130:
|
elif returncode == 130:
|
||||||
# Keyboard interupt
|
# Keyboard interrupt
|
||||||
pass
|
pass
|
||||||
else: # Real error
|
else: # Real error
|
||||||
# Create QMessageBox
|
# Create QMessageBox
|
||||||
|
|
|
@ -1489,11 +1489,11 @@
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../views/import_window.py" line="69"/>
|
<location filename="../../views/import_window.py" line="69"/>
|
||||||
<source>Schema upgrade failure, file a bug report with the link in the Misc tab with the following error:
|
<source>Schema upgrade failure, file a bug report with the link in the Misc tab with the following error:
|
||||||
{0}
|
{0}
|
||||||
{1}</source>
|
{1}</source>
|
||||||
<translation>Schema-Upgrade Fehler, erstelle einen Bugreport auf dem Link um "Misc"-Tab, mit folgendem Fehler:
|
<translation>Schema-Upgrade Fehler, erstelle einen Bugreport auf dem Link um "Misc"-Tab, mit folgendem Fehler:
|
||||||
{0}
|
{0}
|
||||||
{1}</translation>
|
{1}</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
@ -2267,4 +2267,4 @@
|
||||||
<translation>Speichere Kennwort in der Vortakonfiguration</translation>
|
<translation>Speichere Kennwort in der Vortakonfiguration</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
|
@ -1491,8 +1491,8 @@
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../views/import_window.py" line="69"/>
|
<location filename="../../views/import_window.py" line="69"/>
|
||||||
<source>Schema upgrade failure, file a bug report with the link in the Misc tab with the following error:
|
<source>Schema upgrade failure, file a bug report with the link in the Misc tab with the following error:
|
||||||
{0}
|
{0}
|
||||||
{1}</source>
|
{1}</source>
|
||||||
<translation>Fallo al actualizar el esquema, rellene un informe de error con el enlace de la pestaña «Varios» con el siguiente error:
|
<translation>Fallo al actualizar el esquema, rellene un informe de error con el enlace de la pestaña «Varios» con el siguiente error:
|
||||||
{0}
|
{0}
|
||||||
|
@ -2269,4 +2269,4 @@
|
||||||
<translation>Guardar contraseñas con los ajustes de Vorta.</translation>
|
<translation>Guardar contraseñas con los ajustes de Vorta.</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
|
@ -1491,11 +1491,11 @@
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../../views/import_window.py" line="69"/>
|
<location filename="../../views/import_window.py" line="69"/>
|
||||||
<source>Schema upgrade failure, file a bug report with the link in the Misc tab with the following error:
|
<source>Schema upgrade failure, file a bug report with the link in the Misc tab with the following error:
|
||||||
{0}
|
{0}
|
||||||
{1}</source>
|
{1}</source>
|
||||||
<translation>Skeeman päivitys epäonnistui, lähetä virheraportti Sekalaiset-välilehdellä olevasta linkistä. Liitä raporttiin seuraavat tiedot:
|
<translation>Skeeman päivitys epäonnistui, lähetä virheraportti Sekalaiset-välilehdellä olevasta linkistä. Liitä raporttiin seuraavat tiedot:
|
||||||
{0}
|
{0}
|
||||||
{1}</translation>
|
{1}</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
@ -2269,4 +2269,4 @@
|
||||||
<translation>Tallennetaan salasana Vortan asetuksiin.</translation>
|
<translation>Tallennetaan salasana Vortan asetuksiin.</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import vorta.borg
|
||||||
import vorta.utils
|
import vorta.utils
|
||||||
import vorta.views.archive_tab
|
import vorta.views.archive_tab
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
|
from PyQt6.QtWidgets import QMenu
|
||||||
from vorta.store.models import ArchiveModel, BackupProfileModel
|
from vorta.store.models import ArchiveModel, BackupProfileModel
|
||||||
|
|
||||||
|
|
||||||
|
@ -202,3 +203,18 @@ def test_inline_archive_rename(qapp, qtbot, mocker, borg_json_output, archive_en
|
||||||
# Successful rename case
|
# Successful rename case
|
||||||
qtbot.waitUntil(lambda: tab.archiveTable.model().index(0, 4).data() == new_archive_name, **pytest._wait_defaults)
|
qtbot.waitUntil(lambda: tab.archiveTable.model().index(0, 4).data() == new_archive_name, **pytest._wait_defaults)
|
||||||
assert tab.archiveTable.model().index(0, 4).data() == new_archive_name
|
assert tab.archiveTable.model().index(0, 4).data() == new_archive_name
|
||||||
|
|
||||||
|
|
||||||
|
def test_archiveitem_contextmenu(qapp, qtbot, archive_env):
|
||||||
|
main, tab = archive_env
|
||||||
|
|
||||||
|
pos = tab.archiveTable.visualRect(tab.archiveTable.model().index(0, 0)).center()
|
||||||
|
tab.archiveTable.customContextMenuRequested.emit(pos)
|
||||||
|
qtbot.waitUntil(lambda: tab.archiveTable.findChild(QMenu) is not None, timeout=2000)
|
||||||
|
|
||||||
|
context_menu = tab.archiveTable.findChild(QMenu)
|
||||||
|
|
||||||
|
assert context_menu is not None
|
||||||
|
expected_actions = ['Copy', 'Recalculate', 'Mount…', 'Extract…', 'Rename…', 'Delete', 'Diff']
|
||||||
|
for action in expected_actions:
|
||||||
|
assert any(menu_actions.text() == action for menu_actions in context_menu.actions())
|
||||||
|
|
|
@ -5,6 +5,7 @@ import vorta.borg
|
||||||
import vorta.utils
|
import vorta.utils
|
||||||
import vorta.views.archive_tab
|
import vorta.views.archive_tab
|
||||||
from PyQt6.QtCore import QDateTime, QItemSelectionModel, Qt
|
from PyQt6.QtCore import QDateTime, QItemSelectionModel, Qt
|
||||||
|
from PyQt6.QtWidgets import QMenu
|
||||||
from vorta.views.diff_result import (
|
from vorta.views.diff_result import (
|
||||||
ChangeType,
|
ChangeType,
|
||||||
DiffData,
|
DiffData,
|
||||||
|
@ -15,12 +16,8 @@ from vorta.views.diff_result import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
def setup_diff_result_window(qtbot, mocker, tab, borg_json_output, json_mock_file="diff_archives"):
|
||||||
'json_mock_file,folder_root', [('diff_archives', 'test'), ('diff_archives_dict_issue', 'Users')]
|
"""Sets up the diff result window."""
|
||||||
)
|
|
||||||
def test_archive_diff(qapp, qtbot, mocker, borg_json_output, json_mock_file, folder_root, archive_env):
|
|
||||||
main, tab = archive_env
|
|
||||||
|
|
||||||
stdout, stderr = borg_json_output(json_mock_file)
|
stdout, stderr = borg_json_output(json_mock_file)
|
||||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
||||||
mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result)
|
mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result)
|
||||||
|
@ -46,14 +43,70 @@ def test_archive_diff(qapp, qtbot, mocker, borg_json_output, json_mock_file, fol
|
||||||
tab.diff_action()
|
tab.diff_action()
|
||||||
|
|
||||||
qtbot.waitUntil(lambda: hasattr(tab, '_resultwindow'), **pytest._wait_defaults)
|
qtbot.waitUntil(lambda: hasattr(tab, '_resultwindow'), **pytest._wait_defaults)
|
||||||
|
assert hasattr(tab, '_resultwindow')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'json_mock_file, folder_root', [('diff_archives', 'test'), ('diff_archives_dict_issue', 'Users')]
|
||||||
|
)
|
||||||
|
def test_archive_diff(qapp, qtbot, mocker, borg_json_output, json_mock_file, folder_root, archive_env):
|
||||||
|
"""Tests basic functionality of archive diff."""
|
||||||
|
main, tab = archive_env
|
||||||
|
setup_diff_result_window(qtbot, mocker, tab, borg_json_output, json_mock_file)
|
||||||
|
|
||||||
model = tab._resultwindow.treeView.model().sourceModel()
|
model = tab._resultwindow.treeView.model().sourceModel()
|
||||||
assert model.root.children[0].subpath == folder_root
|
assert model.root.children[0].subpath == folder_root
|
||||||
|
|
||||||
assert tab._resultwindow.archiveNameLabel_1.text() == 'test-archive'
|
assert tab._resultwindow.archiveNameLabel_1.text() == 'test-archive'
|
||||||
tab._resultwindow.accept()
|
tab._resultwindow.accept()
|
||||||
|
|
||||||
|
|
||||||
|
def test_diff_item_copy(qapp, qtbot, mocker, borg_json_output, archive_env):
|
||||||
|
"""Tests copy action by row selection and when passed an index."""
|
||||||
|
main, tab = archive_env
|
||||||
|
setup_diff_result_window(qtbot, mocker, tab, borg_json_output)
|
||||||
|
|
||||||
|
# mock the clipboard to ensure no changes are made to it during testing
|
||||||
|
mocker.patch.object(qapp.clipboard(), "setMimeData")
|
||||||
|
clipboard_spy = mocker.spy(qapp.clipboard(), "setMimeData")
|
||||||
|
|
||||||
|
# test 'diff_item_copy()' by passing it an item to copy
|
||||||
|
index = tab._resultwindow.treeView.model().index(0, 0)
|
||||||
|
assert index is not None
|
||||||
|
tab._resultwindow.diff_item_copy(index)
|
||||||
|
clipboard_data = clipboard_spy.call_args[0][0]
|
||||||
|
assert clipboard_data.hasText()
|
||||||
|
assert clipboard_data.text() == "/test"
|
||||||
|
|
||||||
|
clipboard_spy.reset_mock()
|
||||||
|
|
||||||
|
# test 'diff_item_copy()' by selecting a row to copy
|
||||||
|
flags = QItemSelectionModel.SelectionFlag.Rows
|
||||||
|
flags |= QItemSelectionModel.SelectionFlag.Select
|
||||||
|
tab._resultwindow.treeView.selectionModel().select(tab._resultwindow.treeView.model().index(0, 0), flags)
|
||||||
|
tab._resultwindow.diff_item_copy()
|
||||||
|
clipboard_data = clipboard_spy.call_args[0][0]
|
||||||
|
assert clipboard_data.hasText()
|
||||||
|
assert clipboard_data.text() == "/test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_treeview_context_menu(qapp, qtbot, mocker, borg_json_output, archive_env):
|
||||||
|
"""Tests the diff result window context menu for expected actions."""
|
||||||
|
main, tab = archive_env
|
||||||
|
setup_diff_result_window(qtbot, mocker, tab, borg_json_output)
|
||||||
|
|
||||||
|
# Load the context menu at the first result in window
|
||||||
|
pos = tab._resultwindow.treeView.visualRect(tab._resultwindow.treeView.model().index(0, 0)).center()
|
||||||
|
tab._resultwindow.treeview_context_menu(pos)
|
||||||
|
qtbot.waitUntil(lambda: tab._resultwindow.findChild(QMenu) is not None, **pytest._wait_defaults)
|
||||||
|
context_menu = tab._resultwindow.findChild(QMenu)
|
||||||
|
assert context_menu is not None
|
||||||
|
|
||||||
|
# assert the actions are available in the context menu
|
||||||
|
expected_actions = ['Copy', 'Expand recursively']
|
||||||
|
for action in expected_actions:
|
||||||
|
assert any(menu_actions.text() == action for menu_actions in context_menu.actions())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'line, expected',
|
'line, expected',
|
||||||
[
|
[
|
||||||
|
@ -404,59 +457,3 @@ def test_archive_diff_json_parser(line, expected):
|
||||||
|
|
||||||
assert item.path == PurePath(expected[0]).parts
|
assert item.path == PurePath(expected[0]).parts
|
||||||
assert item.data == DiffData(*expected[1:])
|
assert item.data == DiffData(*expected[1:])
|
||||||
|
|
||||||
|
|
||||||
def test_diff_item_copy(qapp, qtbot, mocker, borg_json_output):
|
|
||||||
main = qapp.main_window
|
|
||||||
tab = main.archiveTab
|
|
||||||
main.tabWidget.setCurrentIndex(3)
|
|
||||||
|
|
||||||
tab.populate_from_profile()
|
|
||||||
qtbot.waitUntil(lambda: tab.archiveTable.rowCount() == 2)
|
|
||||||
|
|
||||||
stdout, stderr = borg_json_output("diff_archives")
|
|
||||||
popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
|
|
||||||
mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result)
|
|
||||||
|
|
||||||
compat = vorta.utils.borg_compat
|
|
||||||
|
|
||||||
def check(feature_name):
|
|
||||||
if feature_name == 'DIFF_JSON_LINES':
|
|
||||||
return False
|
|
||||||
return vorta.utils.BorgCompatibility.check(compat, feature_name)
|
|
||||||
|
|
||||||
mocker.patch.object(vorta.utils.borg_compat, 'check', check)
|
|
||||||
|
|
||||||
selection_model: QItemSelectionModel = tab.archiveTable.selectionModel()
|
|
||||||
model = tab.archiveTable.model()
|
|
||||||
|
|
||||||
flags = QItemSelectionModel.SelectionFlag.Rows
|
|
||||||
flags |= QItemSelectionModel.SelectionFlag.Select
|
|
||||||
|
|
||||||
selection_model.select(model.index(0, 0), flags)
|
|
||||||
selection_model.select(model.index(1, 0), flags)
|
|
||||||
|
|
||||||
tab.diff_action()
|
|
||||||
|
|
||||||
qtbot.waitUntil(lambda: hasattr(tab, '_resultwindow'), **pytest._wait_defaults)
|
|
||||||
|
|
||||||
# mock the clipboard to ensure no changes are made to it during testing
|
|
||||||
mocker.patch.object(qapp.clipboard(), "setMimeData")
|
|
||||||
clipboard_spy = mocker.spy(qapp.clipboard(), "setMimeData")
|
|
||||||
|
|
||||||
# test 'diff_item_copy()' by passing it an item to copy
|
|
||||||
index = tab._resultwindow.treeView.model().index(0, 0)
|
|
||||||
assert index is not None
|
|
||||||
tab._resultwindow.diff_item_copy(index)
|
|
||||||
clipboard_data = clipboard_spy.call_args[0][0]
|
|
||||||
assert clipboard_data.hasText()
|
|
||||||
assert clipboard_data.text() == "/test"
|
|
||||||
|
|
||||||
clipboard_spy.reset_mock()
|
|
||||||
|
|
||||||
# test 'diff_item_copy()' by selecting a row to copy
|
|
||||||
tab._resultwindow.treeView.selectionModel().select(tab._resultwindow.treeView.model().index(0, 0), flags)
|
|
||||||
tab._resultwindow.diff_item_copy()
|
|
||||||
clipboard_data = clipboard_spy.call_args[0][0]
|
|
||||||
assert clipboard_data.hasText()
|
|
||||||
assert clipboard_data.text() == "/test"
|
|
||||||
|
|
|
@ -6,33 +6,74 @@ from unittest.mock import Mock
|
||||||
import pytest
|
import pytest
|
||||||
import vorta.store.models
|
import vorta.store.models
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
from PyQt6.QtWidgets import QCheckBox, QFormLayout
|
from PyQt6.QtGui import QCloseEvent
|
||||||
|
from PyQt6.QtWidgets import QCheckBox, QFormLayout, QMessageBox
|
||||||
|
from vorta.store.models import SettingsModel
|
||||||
|
|
||||||
|
|
||||||
def test_autostart(qapp, qtbot):
|
def test_toggle_all_settings(qapp, qtbot):
|
||||||
"""Check if file exists only on Linux, otherwise just check it doesn't crash"""
|
"""Toggle each setting twice as a basic sanity test to ensure app does crash."""
|
||||||
|
groups = (
|
||||||
|
SettingsModel.select(SettingsModel.group)
|
||||||
|
.distinct(True)
|
||||||
|
.where(SettingsModel.group != '')
|
||||||
|
.order_by(SettingsModel.group.asc())
|
||||||
|
)
|
||||||
|
|
||||||
|
settings = [
|
||||||
|
setting
|
||||||
|
for group in groups
|
||||||
|
for setting in SettingsModel.select().where(
|
||||||
|
SettingsModel.type == 'checkbox', SettingsModel.group == group.group
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
for setting in settings:
|
||||||
|
for _ in range(2):
|
||||||
|
_click_toggle_setting(setting.label, qapp, qtbot)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.platform != "linux", reason="testing autostart path for Linux only")
|
||||||
|
def test_autostart_linux(qapp, qtbot):
|
||||||
|
"""Checks that autostart path is added correctly on Linux when setting is enabled."""
|
||||||
setting = "Automatically start Vorta at login"
|
setting = "Automatically start Vorta at login"
|
||||||
|
|
||||||
|
# ensure file is present when autostart is enabled
|
||||||
_click_toggle_setting(setting, qapp, qtbot)
|
_click_toggle_setting(setting, qapp, qtbot)
|
||||||
|
autostart_path = (
|
||||||
|
Path(os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~") + '/.config') + "/autostart") / "vorta.desktop"
|
||||||
|
)
|
||||||
|
qtbot.waitUntil(lambda: autostart_path.exists(), **pytest._wait_defaults)
|
||||||
|
with open(autostart_path) as desktop_file:
|
||||||
|
desktop_file_text = desktop_file.read()
|
||||||
|
assert desktop_file_text.startswith("[Desktop Entry]")
|
||||||
|
|
||||||
if sys.platform == 'linux':
|
# ensure file is removed when autostart is disabled
|
||||||
autostart_path = (
|
|
||||||
Path(os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~") + '/.config') + "/autostart")
|
|
||||||
/ "vorta.desktop"
|
|
||||||
)
|
|
||||||
qtbot.waitUntil(lambda: autostart_path.exists(), **pytest._wait_defaults)
|
|
||||||
|
|
||||||
with open(autostart_path) as desktop_file:
|
|
||||||
desktop_file_text = desktop_file.read()
|
|
||||||
|
|
||||||
assert desktop_file_text.startswith("[Desktop Entry]")
|
|
||||||
|
|
||||||
_click_toggle_setting(setting, qapp, qtbot)
|
_click_toggle_setting(setting, qapp, qtbot)
|
||||||
|
|
||||||
if sys.platform == 'linux':
|
if sys.platform == 'linux':
|
||||||
assert not os.path.exists(autostart_path)
|
assert not os.path.exists(autostart_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_enable_background_question(qapp, monkeypatch, mocker):
|
||||||
|
"""Tests that 'enable background question' correctly prompts user."""
|
||||||
|
main = qapp.main_window
|
||||||
|
close_event = Mock(value=QCloseEvent())
|
||||||
|
|
||||||
|
# disable system trey and enable setting to test
|
||||||
|
monkeypatch.setattr("vorta.views.main_window.is_system_tray_available", lambda: False)
|
||||||
|
mocker.patch.object(vorta.store.models.SettingsModel, "get", return_value=Mock(value=True))
|
||||||
|
mocker.patch.object(QMessageBox, "exec") # prevent QMessageBox from stopping test
|
||||||
|
|
||||||
|
# Create a mock for QMessageBox and its setText method
|
||||||
|
mock_msgbox = mocker.Mock(spec=QMessageBox)
|
||||||
|
mocker.patch("vorta.views.main_window.QMessageBox", return_value=mock_msgbox)
|
||||||
|
|
||||||
|
main.closeEvent(close_event)
|
||||||
|
|
||||||
|
mock_msgbox.setText.assert_called_once_with("Should Vorta continue to run in the background?")
|
||||||
|
close_event.accept.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_enable_fixed_units(qapp, qtbot, mocker):
|
def test_enable_fixed_units(qapp, qtbot, mocker):
|
||||||
"""Tests the 'enable fixed units' setting to ensure the archive tab sizes are displayed correctly."""
|
"""Tests the 'enable fixed units' setting to ensure the archive tab sizes are displayed correctly."""
|
||||||
tab = qapp.main_window.archiveTab
|
tab = qapp.main_window.archiveTab
|
||||||
|
@ -61,14 +102,13 @@ def test_enable_fixed_units(qapp, qtbot, mocker):
|
||||||
assert kwargs_list['fixed_unit'] is None
|
assert kwargs_list['fixed_unit'] is None
|
||||||
|
|
||||||
# use the qt bot to click the setting and see that the refresh_archive emit works as intended.
|
# use the qt bot to click the setting and see that the refresh_archive emit works as intended.
|
||||||
with qtbot.waitSignal(qapp.main_window.miscTab.refresh_archive, timeout=5000):
|
with qtbot.waitSignal(qapp.main_window.miscTab.refresh_archive, **pytest._wait_defaults):
|
||||||
_click_toggle_setting(setting, qapp, qtbot)
|
_click_toggle_setting(setting, qapp, qtbot)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform != 'darwin', reason="Full Disk Access check only on Darwin")
|
@pytest.mark.skipif(sys.platform != 'darwin', reason="Full Disk Access check only on Darwin")
|
||||||
def test_check_full_disk_access(qapp, qtbot, mocker):
|
def test_check_full_disk_access(qapp, qtbot, mocker):
|
||||||
"""Enables/disables 'Check for Full Disk Access on startup' setting and ensures functionality"""
|
"""Tests if the full disk access warning is properly silenced with the setting enabled"""
|
||||||
setting = "Check for Full Disk Access on startup"
|
|
||||||
|
|
||||||
# Set mocks for setting enabled
|
# Set mocks for setting enabled
|
||||||
mocker.patch.object(vorta.store.models.SettingsModel, "get", return_value=Mock(value=True))
|
mocker.patch.object(vorta.store.models.SettingsModel, "get", return_value=Mock(value=True))
|
||||||
|
@ -88,10 +128,6 @@ def test_check_full_disk_access(qapp, qtbot, mocker):
|
||||||
qapp.check_darwin_permissions()
|
qapp.check_darwin_permissions()
|
||||||
mock_qmessagebox.assert_not_called()
|
mock_qmessagebox.assert_not_called()
|
||||||
|
|
||||||
# Checks that setting doesn't crash program when click toggled on then off"""
|
|
||||||
_click_toggle_setting(setting, qapp, qtbot)
|
|
||||||
_click_toggle_setting(setting, qapp, qtbot)
|
|
||||||
|
|
||||||
|
|
||||||
def _click_toggle_setting(setting, qapp, qtbot):
|
def _click_toggle_setting(setting, qapp, qtbot):
|
||||||
"""Toggle setting checkbox in the misc tab"""
|
"""Toggle setting checkbox in the misc tab"""
|
||||||
|
|
|
@ -1,37 +1,49 @@
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
from PyQt6.QtWidgets import QDialogButtonBox
|
from PyQt6.QtWidgets import QDialogButtonBox, QMessageBox, QToolTip
|
||||||
from vorta.store.models import BackupProfileModel
|
from vorta.store.models import BackupProfileModel
|
||||||
|
|
||||||
|
|
||||||
def test_profile_add(qapp, qtbot):
|
def test_profile_add_delete(qapp, qtbot, mocker):
|
||||||
|
"""Tests adding and deleting profiles."""
|
||||||
main = qapp.main_window
|
main = qapp.main_window
|
||||||
|
|
||||||
|
# add profile and ensure it is created as intended
|
||||||
qtbot.mouseClick(main.profileAddButton, QtCore.Qt.MouseButton.LeftButton)
|
qtbot.mouseClick(main.profileAddButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
|
||||||
add_profile_window = main.window
|
add_profile_window = main.window
|
||||||
# qtbot.addWidget(add_profile_window)
|
|
||||||
|
|
||||||
qtbot.keyClicks(add_profile_window.profileNameField, 'Test Profile')
|
qtbot.keyClicks(add_profile_window.profileNameField, 'Test Profile')
|
||||||
qtbot.mouseClick(
|
save_button = add_profile_window.buttonBox.button(QDialogButtonBox.StandardButton.Save)
|
||||||
add_profile_window.buttonBox.button(QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton
|
qtbot.mouseClick(save_button, QtCore.Qt.MouseButton.LeftButton)
|
||||||
)
|
|
||||||
|
|
||||||
assert BackupProfileModel.get_or_none(name='Test Profile') is not None
|
assert BackupProfileModel.get_or_none(name='Test Profile') is not None
|
||||||
assert main.profileSelector.currentText() == 'Test Profile'
|
assert main.profileSelector.currentText() == 'Test Profile'
|
||||||
|
|
||||||
|
# delete the new profile and ensure it is no longer available.
|
||||||
|
mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.StandardButton.Yes)
|
||||||
|
qtbot.mouseClick(main.profileDeleteButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
assert BackupProfileModel.get_or_none(name='Test Profile') is None
|
||||||
|
assert main.profileSelector.currentText() == 'Default'
|
||||||
|
|
||||||
|
# attempt to delete the last remaining profile
|
||||||
|
# see that it cannot be deleted, a warning is displayed, and the profile remains
|
||||||
|
warning = mocker.patch.object(QToolTip, 'showText')
|
||||||
|
qtbot.mouseClick(main.profileDeleteButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
assert "Cannot delete the last profile." in warning.call_args[0][1]
|
||||||
|
assert BackupProfileModel.get_or_none(name='Default') is not None
|
||||||
|
assert main.profileSelector.currentText() == 'Default'
|
||||||
|
|
||||||
|
|
||||||
def test_profile_edit(qapp, qtbot):
|
def test_profile_edit(qapp, qtbot):
|
||||||
|
"""Tests editing/renaming a profile"""
|
||||||
main = qapp.main_window
|
main = qapp.main_window
|
||||||
|
|
||||||
|
# click to rename profile, clear the name field, type new profile name
|
||||||
qtbot.mouseClick(main.profileRenameButton, QtCore.Qt.MouseButton.LeftButton)
|
qtbot.mouseClick(main.profileRenameButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
|
||||||
edit_profile_window = main.window
|
edit_profile_window = main.window
|
||||||
# qtbot.addWidget(edit_profile_window)
|
|
||||||
|
|
||||||
edit_profile_window.profileNameField.setText("")
|
edit_profile_window.profileNameField.setText("")
|
||||||
qtbot.keyClicks(edit_profile_window.profileNameField, 'Test Profile')
|
qtbot.keyClicks(edit_profile_window.profileNameField, 'Test Profile')
|
||||||
qtbot.mouseClick(
|
save_button = edit_profile_window.buttonBox.button(QDialogButtonBox.StandardButton.Save)
|
||||||
edit_profile_window.buttonBox.button(QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton
|
qtbot.mouseClick(save_button, QtCore.Qt.MouseButton.LeftButton)
|
||||||
)
|
|
||||||
|
|
||||||
|
# assert a profile by the old name no longer exists, and the newly named profile does exist and is selected.
|
||||||
assert BackupProfileModel.get_or_none(name='Default') is None
|
assert BackupProfileModel.get_or_none(name='Default') is None
|
||||||
assert BackupProfileModel.get_or_none(name='Test Profile') is not None
|
assert BackupProfileModel.get_or_none(name='Test Profile') is not None
|
||||||
assert main.profileSelector.currentText() == 'Test Profile'
|
assert main.profileSelector.currentText() == 'Test Profile'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import vorta.borg.borg_job
|
import vorta.borg.borg_job
|
||||||
|
@ -187,6 +188,55 @@ def test_ssh_dialog_failure(qapp, qtbot, mocker, monkeypatch, tmpdir):
|
||||||
assert tab.sshComboBox.count() == 1
|
assert tab.sshComboBox.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_ssh_copy_to_clipboard_action(qapp, qtbot, mocker, tmpdir):
|
||||||
|
"""Testing the proper QMessageBox dialogue appears depending on the copy action circumstances."""
|
||||||
|
tab = qapp.main_window.repoTab
|
||||||
|
|
||||||
|
# set mocks to test assertions and prevent test interruptions
|
||||||
|
text = mocker.patch.object(QMessageBox, "setText")
|
||||||
|
mocker.patch.object(QMessageBox, "show")
|
||||||
|
mocker.patch.object(qapp.clipboard(), "setText")
|
||||||
|
|
||||||
|
qtbot.mouseClick(tab.bAddSSHKey, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
ssh_dialog = tab._window
|
||||||
|
ssh_dialog_closed = mocker.spy(ssh_dialog, 'reject')
|
||||||
|
ssh_dir = tmpdir
|
||||||
|
key_tmpfile = ssh_dir.join("id_rsa-test")
|
||||||
|
pub_tmpfile = ssh_dir.join("id_rsa-test.pub")
|
||||||
|
key_tmpfile_full = os.path.join(key_tmpfile.dirname, key_tmpfile.basename)
|
||||||
|
ssh_dialog.outputFileTextBox.setText(key_tmpfile_full)
|
||||||
|
ssh_dialog.generate_key()
|
||||||
|
|
||||||
|
# Ensure new key file was created
|
||||||
|
qtbot.waitUntil(lambda: ssh_dialog_closed.called, **pytest._wait_defaults)
|
||||||
|
assert len(ssh_dir.listdir()) == 2
|
||||||
|
# populate the ssh combobox with the ssh key we created in tmpdir
|
||||||
|
mock_expanduser = mocker.patch('os.path.expanduser', return_value=str(tmpdir))
|
||||||
|
tab.init_ssh()
|
||||||
|
assert tab.sshComboBox.count() == 2
|
||||||
|
|
||||||
|
# test when no ssh key is selected to copy
|
||||||
|
assert tab.sshComboBox.currentIndex() == 0
|
||||||
|
qtbot.mouseClick(tab.sshKeyToClipboardButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
message = "Select a public key from the dropdown first."
|
||||||
|
text.assert_called_with(message)
|
||||||
|
|
||||||
|
# Select a key and copy it
|
||||||
|
mock_expanduser.return_value = pub_tmpfile
|
||||||
|
tab.sshComboBox.setCurrentIndex(1)
|
||||||
|
assert tab.sshComboBox.currentIndex() == 1
|
||||||
|
qtbot.mouseClick(tab.sshKeyToClipboardButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
message = "The selected public SSH key was copied to the clipboard. Use it to set up remote repo permissions."
|
||||||
|
text.assert_called_with(message)
|
||||||
|
|
||||||
|
# handle ssh key file not found
|
||||||
|
mock_expanduser.return_value = "foobar"
|
||||||
|
assert tab.sshComboBox.currentIndex() == 1
|
||||||
|
qtbot.mouseClick(tab.sshKeyToClipboardButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
|
message = "Could not find public key."
|
||||||
|
text.assert_called_with(message)
|
||||||
|
|
||||||
|
|
||||||
def test_create(qapp, borg_json_output, mocker, qtbot):
|
def test_create(qapp, borg_json_output, mocker, qtbot):
|
||||||
main = qapp.main_window
|
main = qapp.main_window
|
||||||
stdout, stderr = borg_json_output('create')
|
stdout, stderr = borg_json_output('create')
|
||||||
|
@ -202,3 +252,56 @@ def test_create(qapp, borg_json_output, mocker, qtbot):
|
||||||
assert main.createStartBtn.isEnabled()
|
assert main.createStartBtn.isEnabled()
|
||||||
assert main.archiveTab.archiveTable.rowCount() == 3
|
assert main.archiveTab.archiveTable.rowCount() == 3
|
||||||
assert main.scheduleTab.logTableWidget.rowCount() == 1
|
assert main.scheduleTab.logTableWidget.rowCount() == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"response",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"return_code": 0, # no error
|
||||||
|
"error": "",
|
||||||
|
"icon": None,
|
||||||
|
"info": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"return_code": 1, # warning
|
||||||
|
"error": "Borg exited with warning status (rc 1).",
|
||||||
|
"icon": QMessageBox.Icon.Warning,
|
||||||
|
"info": "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"return_code": 2, # critical error
|
||||||
|
"error": "Repository data check for repo test_repo_url failed. Error code 2",
|
||||||
|
"icon": QMessageBox.Icon.Critical,
|
||||||
|
"info": "Consider repairing or recreating the repository soon to avoid missing data.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"return_code": 135, # 128 + n = kill signal n
|
||||||
|
"error": "killed by signal 7",
|
||||||
|
"icon": QMessageBox.Icon.Critical,
|
||||||
|
"info": "The process running the check job got a kill signal. Try again.",
|
||||||
|
},
|
||||||
|
{"return_code": 130, "error": "", "icon": None, "info": None}, # keyboard interrupt
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_repo_check_failed_response(qapp, qtbot, mocker, response):
|
||||||
|
"""Test the processing of the signal that a repo consistency check has failed."""
|
||||||
|
mock_result: Dict[str, Any] = {
|
||||||
|
'params': {'repo_url': 'test_repo_url'},
|
||||||
|
'returncode': response["return_code"],
|
||||||
|
'errors': [(0, 'test_error_message')] if response["return_code"] not in [0, 130] else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_exec = mocker.patch.object(QMessageBox, "exec")
|
||||||
|
mock_text = mocker.patch.object(QMessageBox, "setText")
|
||||||
|
mock_info = mocker.patch.object(QMessageBox, "setInformativeText")
|
||||||
|
mock_icon = mocker.patch.object(QMessageBox, "setIcon")
|
||||||
|
|
||||||
|
qapp.check_failed_response(mock_result)
|
||||||
|
|
||||||
|
# return codes 0 and 130 do not provide a message
|
||||||
|
# for all other return codes, assert the message is formatted correctly
|
||||||
|
if mock_exec.call_count != 0:
|
||||||
|
mock_icon.assert_called_with(response["icon"])
|
||||||
|
assert response["error"] in mock_text.call_args[0][0]
|
||||||
|
assert response["info"] in mock_info.call_args[0][0]
|
||||||
|
|
|
@ -2,6 +2,8 @@ import pytest
|
||||||
import vorta.views
|
import vorta.views
|
||||||
from PyQt6 import QtCore
|
from PyQt6 import QtCore
|
||||||
from PyQt6.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
from vorta.views.main_window import MainWindow
|
||||||
|
from vorta.views.source_tab import SourceTab
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
@ -9,11 +11,11 @@ def source_env(qapp, qtbot, monkeypatch, choose_file_dialog):
|
||||||
"""
|
"""
|
||||||
Handles common setup and teardown for unit tests involving the source tab.
|
Handles common setup and teardown for unit tests involving the source tab.
|
||||||
"""
|
"""
|
||||||
monkeypatch.setattr(vorta.views.source_tab, "choose_file_dialog", choose_file_dialog)
|
main: MainWindow = qapp.main_window
|
||||||
main = qapp.main_window
|
tab: SourceTab = main.sourceTab
|
||||||
main.tabWidget.setCurrentIndex(1)
|
main.tabWidget.setCurrentIndex(1)
|
||||||
tab = main.sourceTab
|
|
||||||
qtbot.waitUntil(lambda: tab.sourceFilesWidget.rowCount() == 1, timeout=2000)
|
qtbot.waitUntil(lambda: tab.sourceFilesWidget.rowCount() == 1, timeout=2000)
|
||||||
|
monkeypatch.setattr(vorta.views.source_tab, "choose_file_dialog", choose_file_dialog)
|
||||||
|
|
||||||
yield main, tab
|
yield main, tab
|
||||||
|
|
||||||
|
@ -100,3 +102,27 @@ def test_sources_update(qapp, qtbot, mocker, source_env):
|
||||||
qtbot.mouseClick(tab.updateButton, QtCore.Qt.MouseButton.LeftButton)
|
qtbot.mouseClick(tab.updateButton, QtCore.Qt.MouseButton.LeftButton)
|
||||||
assert tab.sourceFilesWidget.rowCount() == 2
|
assert tab.sourceFilesWidget.rowCount() == 2
|
||||||
assert update_path_info_spy.call_count == 2
|
assert update_path_info_spy.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_source_copy(qapp, qtbot, monkeypatch, mocker, source_env):
|
||||||
|
"""
|
||||||
|
Test source_copy() with and without an index passed.
|
||||||
|
If no index is passed, it should copy the first selected source
|
||||||
|
"""
|
||||||
|
main, tab = source_env
|
||||||
|
|
||||||
|
mock_clipboard = mocker.patch.object(qapp.clipboard(), "setMimeData")
|
||||||
|
tab.source_add(want_folder=True)
|
||||||
|
qtbot.waitUntil(lambda: tab.sourceFilesWidget.rowCount() == 2, **pytest._wait_defaults)
|
||||||
|
|
||||||
|
tab.sourceFilesWidget.selectRow(0)
|
||||||
|
tab.source_copy()
|
||||||
|
assert mock_clipboard.call_count == 1
|
||||||
|
source = mock_clipboard.call_args[0][0] # retrieves the QMimeData() object used in method call
|
||||||
|
assert source.text() == "/tmp"
|
||||||
|
|
||||||
|
index = tab.sourceFilesWidget.model().index(1, 0)
|
||||||
|
tab.source_copy(index)
|
||||||
|
assert mock_clipboard.call_count == 2
|
||||||
|
source = mock_clipboard.call_args[0][0] # retrieves the QMimeData() object used in method call
|
||||||
|
assert source.text() == "/tmp/another"
|
||||||
|
|
|
@ -9,6 +9,7 @@ from vorta.utils import (
|
||||||
is_system_tray_available,
|
is_system_tray_available,
|
||||||
normalize_path,
|
normalize_path,
|
||||||
pretty_bytes,
|
pretty_bytes,
|
||||||
|
sort_sizes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +22,33 @@ def test_keyring():
|
||||||
assert keyring.get_password("vorta-repo", REPO) == UNICODE_PW
|
assert keyring.get_password("vorta-repo", REPO) == UNICODE_PW
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"input_sizes, expected_sorted",
|
||||||
|
[
|
||||||
|
# Basic ordering
|
||||||
|
(["1.0 GB", "2.0 MB", "3.0 KB"], ["3.0 KB", "2.0 MB", "1.0 GB"]),
|
||||||
|
# Multiple same units
|
||||||
|
(["3.0 GB", "2.0 GB", "1.0 GB"], ["1.0 GB", "2.0 GB", "3.0 GB"]),
|
||||||
|
# Multiple different units
|
||||||
|
(["2.0 MB", "3.0 GB", "1.0 KB", "5.0 GB"], ["1.0 KB", "2.0 MB", "3.0 GB", "5.0 GB"]),
|
||||||
|
# Larger to smaller units
|
||||||
|
(["1.0 YB", "1.0 ZB", "1.0 EB", "1.0 PB"], ["1.0 PB", "1.0 EB", "1.0 ZB", "1.0 YB"]),
|
||||||
|
# Skipping non-numeric sizes
|
||||||
|
(["2x MB", "3.0 KB", "apple GB", "1.0 GB"], ["3.0 KB", "1.0 GB"]),
|
||||||
|
# Skipping invalid suffix
|
||||||
|
(["1.0 XX", "5.0 YY", "9.0 ZZ", "1.0 MB"], ["1.0 MB"]),
|
||||||
|
# Floats with decimals
|
||||||
|
(["2.5 GB", "2.3 GB", "1.1 MB"], ["1.1 MB", "2.3 GB", "2.5 GB"]),
|
||||||
|
# Checking the same sizes across different units
|
||||||
|
(["1.0 MB", "1000.0 KB"], ["1000.0 KB", "1.0 MB"]),
|
||||||
|
# Handle empty lists
|
||||||
|
([], []),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_sort_sizes(input_sizes, expected_sorted):
|
||||||
|
assert sort_sizes(input_sizes) == expected_sorted
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"precision, expected_unit",
|
"precision, expected_unit",
|
||||||
[
|
[
|
||||||
|
@ -60,7 +88,7 @@ def test_best_unit_for_sizes_nonmetric(sizes, expected_unit):
|
||||||
)
|
)
|
||||||
def test_pretty_bytes_fixed_units(size, metric, precision, fixed_unit, expected_output):
|
def test_pretty_bytes_fixed_units(size, metric, precision, fixed_unit, expected_output):
|
||||||
"""
|
"""
|
||||||
test pretty bytes when specifying a fixed unit of measurement
|
Test pretty bytes when specifying a fixed unit of measurement
|
||||||
"""
|
"""
|
||||||
output = pretty_bytes(size, metric=metric, precision=precision, fixed_unit=fixed_unit)
|
output = pretty_bytes(size, metric=metric, precision=precision, fixed_unit=fixed_unit)
|
||||||
assert output == expected_output
|
assert output == expected_output
|
||||||
|
@ -131,7 +159,7 @@ def test_get_path_datasize(tmpdir):
|
||||||
|
|
||||||
def test_is_system_tray_available(mocker):
|
def test_is_system_tray_available(mocker):
|
||||||
"""
|
"""
|
||||||
sanity check to ensure proper behavior
|
Sanity check to ensure proper behavior
|
||||||
"""
|
"""
|
||||||
mocker.patch('PyQt6.QtWidgets.QSystemTrayIcon.isSystemTrayAvailable', return_value=False)
|
mocker.patch('PyQt6.QtWidgets.QSystemTrayIcon.isSystemTrayAvailable', return_value=False)
|
||||||
assert is_system_tray_available() is False
|
assert is_system_tray_available() is False
|
||||||
|
|
Loading…
Reference in New Issue