mirror of
https://github.com/borgbase/vorta
synced 2024-12-22 15:57:34 +00:00
bde55188e4
Our `DiffResultDialog` and `ExtractDialog` show a context menu for items of the list/tree view. The collapse action in this menu only makes sense for the tree mode of the view. This commit therefore enables the option only for this view mode. * src/vorta/views/extract_dialog.py * src/vorta/views/diff_result.py * tests/unit/test_diff.py : Add tests for the new behaviour. * tests/unit/test_extract.py
478 lines
16 KiB
Python
478 lines
16 KiB
Python
from pathlib import PurePath
|
|
|
|
import pytest
|
|
import vorta.borg
|
|
import vorta.utils
|
|
import vorta.views.archive_tab
|
|
from PyQt6.QtCore import QDateTime, QItemSelectionModel, Qt
|
|
from PyQt6.QtWidgets import QMenu
|
|
from vorta.store.models import ArchiveModel
|
|
from vorta.views.diff_result import (
|
|
ChangeType,
|
|
DiffData,
|
|
DiffResultDialog,
|
|
DiffTree,
|
|
FileType,
|
|
parse_diff_json,
|
|
parse_diff_lines,
|
|
)
|
|
from vorta.views.partials.treemodel import FileTreeModel
|
|
|
|
|
|
def setup_diff_result_window(qtbot, mocker, tab, borg_json_output, json_mock_file="diff_archives"):
|
|
"""Sets up the diff result window."""
|
|
stdout, stderr = borg_json_output(json_mock_file)
|
|
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)
|
|
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()
|
|
assert model.root.children[0].subpath == folder_root
|
|
assert tab._resultwindow.archiveNameLabel_1.text() == 'test-archive'
|
|
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(
|
|
'line, expected',
|
|
[
|
|
(
|
|
'changed link some/changed/link',
|
|
('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0, 0, None, None, None, None, None),
|
|
),
|
|
(
|
|
' +77.8 kB -77.8 kB some/changed/file',
|
|
(
|
|
'some/changed/file',
|
|
FileType.FILE,
|
|
ChangeType.MODIFIED,
|
|
2 * 77800,
|
|
0,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
(77800, 77800),
|
|
),
|
|
),
|
|
(
|
|
' +77.8 kB -77.8 kB [-rw-rw-rw- -> -rw-r--r--] some/changed/file',
|
|
(
|
|
'some/changed/file',
|
|
FileType.FILE,
|
|
ChangeType.MODIFIED,
|
|
2 * 77800,
|
|
0,
|
|
('-rw-rw-rw-', '-rw-r--r--'),
|
|
None,
|
|
None,
|
|
None,
|
|
(77800, 77800),
|
|
),
|
|
),
|
|
(
|
|
'[-rw-rw-rw- -> -rw-r--r--] some/changed/file',
|
|
(
|
|
'some/changed/file',
|
|
FileType.FILE,
|
|
ChangeType.MODE,
|
|
0,
|
|
0,
|
|
('-rw-rw-rw-', '-rw-r--r--'),
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
),
|
|
),
|
|
(
|
|
'added directory some/changed/dir',
|
|
('some/changed/dir', FileType.DIRECTORY, ChangeType.ADDED, 0, 0, None, None, None, None, None),
|
|
),
|
|
(
|
|
'removed directory some/changed/dir',
|
|
('some/changed/dir', FileType.DIRECTORY, ChangeType.REMOVED_DIR, 0, 0, None, None, None, None, None),
|
|
),
|
|
# Example from https://github.com/borgbase/vorta/issues/521
|
|
(
|
|
'[user:user -> nfsnobody:nfsnobody] home/user/arrays/test.txt',
|
|
(
|
|
'home/user/arrays/test.txt',
|
|
FileType.FILE,
|
|
ChangeType.OWNER,
|
|
0,
|
|
0,
|
|
None,
|
|
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
|
None,
|
|
None,
|
|
None,
|
|
),
|
|
),
|
|
# Very short owner change, to check stripping whitespace from file path
|
|
(
|
|
'[a:a -> b:b] home/user/arrays/test.txt',
|
|
(
|
|
'home/user/arrays/test.txt',
|
|
FileType.FILE,
|
|
ChangeType.OWNER,
|
|
0,
|
|
0,
|
|
None,
|
|
('a', 'a', 'b', 'b'),
|
|
None,
|
|
None,
|
|
None,
|
|
),
|
|
),
|
|
# All file-related changes in one test
|
|
(
|
|
' +77.8 kB -800 B [user:user -> nfsnobody:nfsnobody] [-rw-rw-rw- -> -rw-r--r--] home/user/arrays/test.txt',
|
|
(
|
|
'home/user/arrays/test.txt',
|
|
FileType.FILE,
|
|
ChangeType.OWNER,
|
|
77800 + 800,
|
|
77000,
|
|
('-rw-rw-rw-', '-rw-r--r--'),
|
|
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
|
None,
|
|
None,
|
|
(77800, 800),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_archive_diff_parser(line, expected):
|
|
model = DiffTree()
|
|
model.setMode(model.DisplayMode.FLAT)
|
|
parse_diff_lines([line], model)
|
|
|
|
assert model.rowCount() == 1
|
|
item = model.index(0, 0).internalPointer()
|
|
|
|
assert item.path == PurePath(expected[0]).parts
|
|
assert item.data == DiffData(*expected[1:])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'line, expected',
|
|
[
|
|
(
|
|
{'path': 'some/changed/link', 'changes': [{'type': 'changed link'}]},
|
|
('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0, 0, None, None, None, None, None),
|
|
),
|
|
(
|
|
{'path': 'some/changed/file', 'changes': [{'type': 'modified', 'added': 77800, 'removed': 77800}]},
|
|
(
|
|
'some/changed/file',
|
|
FileType.FILE,
|
|
ChangeType.MODIFIED,
|
|
2 * 77800,
|
|
0,
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
(77800, 77800),
|
|
),
|
|
),
|
|
(
|
|
{
|
|
'path': 'some/changed/file',
|
|
'changes': [
|
|
{'type': 'modified', 'added': 77800, 'removed': 800},
|
|
{'type': 'mode', 'old_mode': '-rw-rw-rw-', 'new_mode': '-rw-r--r--'},
|
|
],
|
|
},
|
|
(
|
|
'some/changed/file',
|
|
FileType.FILE,
|
|
ChangeType.MODIFIED,
|
|
77800 + 800,
|
|
77000,
|
|
('-rw-rw-rw-', '-rw-r--r--'),
|
|
None,
|
|
None,
|
|
None,
|
|
(77800, 800),
|
|
),
|
|
),
|
|
(
|
|
{
|
|
'path': 'some/changed/file',
|
|
'changes': [{'type': 'mode', 'old_mode': '-rw-rw-rw-', 'new_mode': '-rw-r--r--'}],
|
|
},
|
|
(
|
|
'some/changed/file',
|
|
FileType.FILE,
|
|
ChangeType.MODE,
|
|
0,
|
|
0,
|
|
('-rw-rw-rw-', '-rw-r--r--'),
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
),
|
|
),
|
|
(
|
|
{'path': 'some/changed/dir', 'changes': [{'type': 'added directory'}]},
|
|
('some/changed/dir', FileType.DIRECTORY, ChangeType.ADDED, 0, 0, None, None, None, None, None),
|
|
),
|
|
(
|
|
{'path': 'some/changed/dir', 'changes': [{'type': 'removed directory'}]},
|
|
('some/changed/dir', FileType.DIRECTORY, ChangeType.REMOVED_DIR, 0, 0, None, None, None, None, None),
|
|
),
|
|
# Example from https://github.com/borgbase/vorta/issues/521
|
|
(
|
|
{
|
|
'path': 'home/user/arrays/test.txt',
|
|
'changes': [
|
|
{
|
|
'type': 'owner',
|
|
'old_user': 'user',
|
|
'new_user': 'nfsnobody',
|
|
'old_group': 'user',
|
|
'new_group': 'nfsnobody',
|
|
}
|
|
],
|
|
},
|
|
(
|
|
'home/user/arrays/test.txt',
|
|
FileType.FILE,
|
|
ChangeType.OWNER,
|
|
0,
|
|
0,
|
|
None,
|
|
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
|
None,
|
|
None,
|
|
None,
|
|
),
|
|
),
|
|
# Very short owner change, to check stripping whitespace from file path
|
|
(
|
|
{
|
|
'path': 'home/user/arrays/test.txt',
|
|
'changes': [{'type': 'owner', 'old_user': 'a', 'new_user': 'b', 'old_group': 'a', 'new_group': 'b'}],
|
|
},
|
|
(
|
|
'home/user/arrays/test.txt',
|
|
FileType.FILE,
|
|
ChangeType.OWNER,
|
|
0,
|
|
0,
|
|
None,
|
|
('a', 'a', 'b', 'b'),
|
|
None,
|
|
None,
|
|
None,
|
|
),
|
|
),
|
|
# Short ctime change
|
|
(
|
|
{
|
|
'path': 'home/user/arrays',
|
|
'changes': [
|
|
{
|
|
'new_ctime': '2023-04-01T17:23:14.104630',
|
|
'old_ctime': '2023-03-03T23:40:17.073948',
|
|
'type': 'ctime',
|
|
}
|
|
],
|
|
},
|
|
(
|
|
'home/user/arrays',
|
|
FileType.FILE,
|
|
ChangeType.MODIFIED,
|
|
0,
|
|
0,
|
|
None,
|
|
None,
|
|
(
|
|
QDateTime.fromString('2023-03-03T23:40:17.073948', Qt.DateFormat.ISODateWithMs),
|
|
QDateTime.fromString('2023-04-01T17:23:14.104630', Qt.DateFormat.ISODateWithMs),
|
|
),
|
|
None,
|
|
None,
|
|
),
|
|
),
|
|
# Short mtime change
|
|
(
|
|
{
|
|
'path': 'home/user/arrays',
|
|
'changes': [
|
|
{
|
|
'new_mtime': '2023-04-01T17:23:14.104630',
|
|
'old_mtime': '2023-03-03T23:40:17.073948',
|
|
'type': 'mtime',
|
|
}
|
|
],
|
|
},
|
|
(
|
|
'home/user/arrays',
|
|
FileType.FILE,
|
|
ChangeType.MODIFIED,
|
|
0,
|
|
0,
|
|
None,
|
|
None,
|
|
None,
|
|
(
|
|
QDateTime.fromString('2023-03-03T23:40:17.073948', Qt.DateFormat.ISODateWithMs),
|
|
QDateTime.fromString('2023-04-01T17:23:14.104630', Qt.DateFormat.ISODateWithMs),
|
|
),
|
|
None,
|
|
),
|
|
),
|
|
# All file-related changes in one test
|
|
(
|
|
{
|
|
'path': 'home/user/arrays/test.txt',
|
|
'changes': [
|
|
{'type': 'modified', 'added': 77800, 'removed': 77800},
|
|
{'type': 'mode', 'old_mode': '-rw-rw-rw-', 'new_mode': '-rw-r--r--'},
|
|
{
|
|
'type': 'owner',
|
|
'old_user': 'user',
|
|
'new_user': 'nfsnobody',
|
|
'old_group': 'user',
|
|
'new_group': 'nfsnobody',
|
|
},
|
|
{
|
|
'new_ctime': '2023-04-01T17:23:14.104630',
|
|
'old_ctime': '2023-03-03T23:40:17.073948',
|
|
'type': 'ctime',
|
|
},
|
|
{
|
|
'new_mtime': '2023-04-01T17:15:50.290565',
|
|
'old_mtime': '2023-03-05T00:24:00.359045',
|
|
'type': 'mtime',
|
|
},
|
|
],
|
|
},
|
|
(
|
|
'home/user/arrays/test.txt',
|
|
FileType.FILE,
|
|
ChangeType.OWNER,
|
|
2 * 77800,
|
|
0,
|
|
('-rw-rw-rw-', '-rw-r--r--'),
|
|
('user', 'user', 'nfsnobody', 'nfsnobody'),
|
|
(
|
|
QDateTime.fromString('2023-03-03T23:40:17.073948', Qt.DateFormat.ISODateWithMs),
|
|
QDateTime.fromString('2023-04-01T17:23:14.104630', Qt.DateFormat.ISODateWithMs),
|
|
),
|
|
(
|
|
QDateTime.fromString('2023-03-05T00:24:00.359045', Qt.DateFormat.ISODateWithMs),
|
|
QDateTime.fromString('2023-04-01T17:15:50.290565', Qt.DateFormat.ISODateWithMs),
|
|
),
|
|
(77800, 77800),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_archive_diff_json_parser(line, expected):
|
|
model = DiffTree()
|
|
model.setMode(model.DisplayMode.FLAT)
|
|
parse_diff_json([line], model)
|
|
|
|
assert model.rowCount() == 1
|
|
item = model.index(0, 0).internalPointer()
|
|
|
|
assert item.path == PurePath(expected[0]).parts
|
|
assert item.data == DiffData(*expected[1:])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"selection, expected_mode, expected_bCollapseAllEnabled",
|
|
[
|
|
(0, FileTreeModel.DisplayMode.TREE, True),
|
|
(1, FileTreeModel.DisplayMode.SIMPLIFIED_TREE, True),
|
|
(2, FileTreeModel.DisplayMode.FLAT, False),
|
|
],
|
|
)
|
|
def test_change_display_mode(selection: int, expected_mode, expected_bCollapseAllEnabled):
|
|
dialog = DiffResultDialog(ArchiveModel(), ArchiveModel(), DiffTree())
|
|
dialog.change_display_mode(selection)
|
|
|
|
assert dialog.model.mode == expected_mode
|
|
assert dialog.bCollapseAll.isEnabled() == expected_bCollapseAllEnabled
|