diff --git a/src/vorta/assets/UI/repoadd.ui b/src/vorta/assets/UI/repoadd.ui index b0f86141..730f1df0 100644 --- a/src/vorta/assets/UI/repoadd.ui +++ b/src/vorta/assets/UI/repoadd.ui @@ -2,14 +2,6 @@ AddRepository - - - 0 - 0 - 583 - 338 - - true @@ -82,7 +74,7 @@ 5 - 5 + 20 @@ -169,47 +161,6 @@ - - - - Borg passphrase: - - - - - - - true - - - QLineEdit::Password - - - - - - - Confirm passphrase: - - - - - - - true - - - QLineEdit::Password - - - - - - - TextLabel - - - @@ -254,23 +205,6 @@ - - - - Encryption: - - - - - - - - 0 - 0 - - - - diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 89510e80..1793a5e8 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -18,7 +18,6 @@ from PyQt6.QtCore import QFileInfo, QThread, pyqtSignal from PyQt6.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon from vorta.borg._compatibility import BorgCompatibility -from vorta.i18n import trans_late from vorta.log import logger from vorta.network_status.abc import NetworkStatusMonitor @@ -507,21 +506,6 @@ def is_system_tray_available(): return is_available -def validate_passwords(first_pass, second_pass): - '''Validates the password for borg, do not use on single fields''' - pass_equal = first_pass == second_pass - pass_long = len(first_pass) > 8 - - if not pass_long and not pass_equal: - return trans_late('utils', "Passwords must be identical and greater than 8 characters long.") - if not pass_equal: - return trans_late('utils', "Passwords must be identical.") - if not pass_long: - return trans_late('utils', "Passwords must be greater than 8 characters long.") - - return "" - - def search(key, iterable: Iterable, func: Callable = None) -> Tuple[int, Any]: """ Search for a key in an iterable. diff --git a/src/vorta/views/partials/password_input.py b/src/vorta/views/partials/password_input.py new file mode 100644 index 00000000..bc8b0dd4 --- /dev/null +++ b/src/vorta/views/partials/password_input.py @@ -0,0 +1,172 @@ +from typing import Optional + +from PyQt6.QtCore import QObject +from PyQt6.QtGui import QAction +from PyQt6.QtWidgets import QFormLayout, QLabel, QLineEdit, QWidget + +from vorta.i18n import translate +from vorta.views.utils import get_colored_icon + + +class PasswordLineEdit(QLineEdit): + def __init__(self, *, parent: Optional[QWidget] = None, show_visibility_button: bool = True) -> None: + super().__init__(parent) + + self._show_visibility_button = show_visibility_button + self._error_state = False + self._visible = False + + self.setEchoMode(QLineEdit.EchoMode.Password) + + if self._show_visibility_button: + self.showHideAction = QAction(self.tr("Show password"), self) + self.showHideAction.setCheckable(True) + self.showHideAction.toggled.connect(self.toggle_visibility) + self.showHideAction.setIcon(get_colored_icon("eye")) + self.addAction(self.showHideAction, QLineEdit.ActionPosition.TrailingPosition) + + def get_password(self) -> str: + """Return password text""" + return self.text() + + @property + def visible(self) -> bool: + """Return password visibility""" + return self._visible + + @visible.setter + def visible(self, value: bool) -> None: + """Set password visibility""" + if not isinstance(value, bool): + raise TypeError("visible must be a boolean value") + self._visible = value + self.setEchoMode(QLineEdit.EchoMode.Normal if self._visible else QLineEdit.EchoMode.Password) + + if self._show_visibility_button: + if self._visible: + self.showHideAction.setIcon(get_colored_icon("eye-slash")) + self.showHideAction.setText(self.tr("Hide password")) + + else: + self.showHideAction.setIcon(get_colored_icon("eye")) + self.showHideAction.setText(self.tr("Show password")) + + def toggle_visibility(self) -> None: + """Toggle password visibility""" + self.visible = not self._visible + + @property + def error_state(self) -> bool: + """Return error state""" + return self._error_state + + @error_state.setter + def error_state(self, error: bool) -> None: + """Set error state and update style""" + self._error_state = error + if error: + self.setStyleSheet("QLineEdit { border: 2px solid red; }") + else: + self.setStyleSheet('') + + +class PasswordInput(QObject): + def __init__(self, *, parent=None, minimum_length: int = 9, show_error: bool = True, label: list = None) -> None: + super().__init__(parent) + self._minimum_length = minimum_length + self._show_error = show_error + + if label: + self._label_password = QLabel(label[0]) + self._label_confirm = QLabel(label[1]) + else: + self._label_password = QLabel(self.tr("Enter passphrase:")) + self._label_confirm = QLabel(self.tr("Confirm passphrase:")) + + # Create password line edits + self.passwordLineEdit = PasswordLineEdit() + self.confirmLineEdit = PasswordLineEdit() + self.validation_label = QLabel("") + + self.passwordLineEdit.editingFinished.connect(self.on_editing_finished) + self.confirmLineEdit.textChanged.connect(self.validate) + + def on_editing_finished(self) -> None: + self.passwordLineEdit.editingFinished.disconnect(self.on_editing_finished) + self.passwordLineEdit.textChanged.connect(self.validate) + self.validate() + + def set_labels(self, label_1: str, label_2: str) -> None: + self._label_password.setText(label_1) + self._label_confirm.setText(label_2) + + def set_error_label(self, text: str) -> None: + self.validation_label.setText(text) + + def set_validation_enabled(self, enable: bool) -> None: + self._show_error = enable + self.passwordLineEdit.error_state = False + self.confirmLineEdit.error_state = False + if not enable: + self.set_error_label("") + + def clear(self) -> None: + self.passwordLineEdit.clear() + self.confirmLineEdit.clear() + self.passwordLineEdit.error_state = False + self.confirmLineEdit.error_state = False + self.set_error_label("") + + def get_password(self) -> str: + return self.passwordLineEdit.text() + + def validate(self) -> bool: + if not self._show_error: + return True + + first_pass = self.passwordLineEdit.text() + second_pass = self.confirmLineEdit.text() + + pass_equal = first_pass == second_pass + pass_long = len(first_pass) >= self._minimum_length + + self.passwordLineEdit.error_state = False + self.confirmLineEdit.error_state = False + self.set_error_label("") + + if not pass_long and not pass_equal: + self.passwordLineEdit.error_state = True + self.confirmLineEdit.error_state = True + self.set_error_label( + translate('PasswordInput', "Passwords must be identical and atleast {0} characters long.").format( + self._minimum_length + ) + ) + elif not pass_equal: + self.confirmLineEdit.error_state = True + self.set_error_label(translate('PasswordInput', "Passwords must be identical.")) + elif not pass_long: + self.passwordLineEdit.error_state = True + self.set_error_label( + translate('PasswordInput', "Passwords must be atleast {0} characters long.").format( + self._minimum_length + ) + ) + + return not bool(self.validation_label.text()) + + def add_form_to_layout(self, form_layout: QFormLayout) -> None: + """Adds form to layout""" + form_layout.addRow(self._label_password, self.passwordLineEdit) + form_layout.addRow(self._label_confirm, self.confirmLineEdit) + form_layout.addRow(self.validation_label) + + def create_form_widget(self, parent: Optional[QWidget] = None) -> QWidget: + """ "Creates and Returns a new QWidget with form layout""" + widget = QWidget(parent=parent) + form_layout = QFormLayout(widget) + form_layout.setContentsMargins(0, 0, 0, 0) + form_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) + self.add_form_to_layout(form_layout) + widget.setLayout(form_layout) + return widget diff --git a/src/vorta/views/repo_add_dialog.py b/src/vorta/views/repo_add_dialog.py index cdb4c3aa..38cc65e2 100644 --- a/src/vorta/views/repo_add_dialog.py +++ b/src/vorta/views/repo_add_dialog.py @@ -1,28 +1,28 @@ import re from PyQt6 import QtCore, uic -from PyQt6.QtGui import QAction -from PyQt6.QtWidgets import QApplication, QDialogButtonBox, QLineEdit +from PyQt6.QtWidgets import ( + QApplication, + QComboBox, + QDialogButtonBox, + QFormLayout, + QLabel, + QSizePolicy, +) from vorta.borg.info_repo import BorgInfoRepoJob from vorta.borg.init import BorgInitJob -from vorta.i18n import translate from vorta.keyring.abc import VortaKeyring from vorta.store.models import RepoModel -from vorta.utils import ( - borg_compat, - choose_file_dialog, - get_asset, - get_private_keys, - validate_passwords, -) +from vorta.utils import borg_compat, choose_file_dialog, get_asset, get_private_keys +from vorta.views.partials.password_input import PasswordInput, PasswordLineEdit from vorta.views.utils import get_colored_icon uifile = get_asset('UI/repoadd.ui') AddRepoUI, AddRepoBase = uic.loadUiType(uifile) -class AddRepoWindow(AddRepoBase, AddRepoUI): +class RepoWindow(AddRepoBase, AddRepoUI): added_repo = QtCore.pyqtSignal(dict) def __init__(self, parent=None): @@ -32,7 +32,8 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self.result = None self.is_remote_repo = True - # dialogButtonBox + self.setMinimumWidth(583) + self.saveButton = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok) self.saveButton.setText(self.tr("Add")) @@ -41,23 +42,11 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self.chooseLocalFolderButton.clicked.connect(self.choose_local_backup_folder) self.useRemoteRepoButton.clicked.connect(self.use_remote_repo_action) self.repoURL.textChanged.connect(self.set_password) - self.passwordLineEdit.textChanged.connect(self.password_listener) - self.confirmLineEdit.textChanged.connect(self.password_listener) - self.encryptionComboBox.activated.connect(self.display_backend_warning) - - # Add clickable icon to toggle password visibility to end of box - self.showHideAction = QAction(self.tr("Show my passwords"), self) - self.showHideAction.setCheckable(True) - self.showHideAction.toggled.connect(self.set_visibility) - - self.passwordLineEdit.addAction(self.showHideAction, QLineEdit.ActionPosition.TrailingPosition) self.tabWidget.setCurrentIndex(0) - self.init_encryption() self.init_ssh_key() self.set_icons() - self.display_backend_warning() def retranslateUi(self, dialog): """Retranslate strings in ui.""" @@ -70,25 +59,6 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): def set_icons(self): self.chooseLocalFolderButton.setIcon(get_colored_icon('folder-open')) self.useRemoteRepoButton.setIcon(get_colored_icon('globe')) - self.showHideAction.setIcon(get_colored_icon("eye")) - - @property - def values(self): - out = dict( - ssh_key=self.sshComboBox.currentData(), - repo_url=self.repoURL.text(), - repo_name=self.repoName.text(), - password=self.passwordLineEdit.text(), - extra_borg_arguments=self.extraBorgArgumentsLineEdit.text(), - ) - if self.__class__ == AddRepoWindow: - out['encryption'] = self.encryptionComboBox.currentData() - return out - - def display_backend_warning(self): - '''Display password backend message based off current keyring''' - if self.encryptionComboBox.currentData() != 'none': - self.passwordLabel.setText(VortaKeyring.get_keyring().get_backend_warning()) def choose_local_backup_folder(self): def receive(): @@ -104,27 +74,6 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): dialog = choose_file_dialog(self, self.tr("Choose Location of Borg Repository")) dialog.open(receive) - def set_password(self, URL): - '''Autofill password from keyring only if current entry is empty''' - password = VortaKeyring.get_keyring().get_password('vorta-repo', URL) - if password and self.passwordLineEdit.text() == "": - self.passwordLabel.setText(self.tr("Autofilled password from password manager.")) - self.passwordLineEdit.setText(password) - if self.__class__ == AddRepoWindow: - self.confirmLineEdit.setText(password) - - def set_visibility(self, visible): - visibility = QLineEdit.EchoMode.Normal if visible else QLineEdit.EchoMode.Password - self.passwordLineEdit.setEchoMode(visibility) - self.confirmLineEdit.setEchoMode(visibility) - - if visible: - self.showHideAction.setIcon(get_colored_icon("eye-slash")) - self.showHideAction.setText(self.tr("Hide my passwords")) - else: - self.showHideAction.setIcon(get_colored_icon("eye")) - self.showHideAction.setText(self.tr("Show my passwords")) - def use_remote_repo_action(self): self.repoURL.setText('') self.repoURL.setEnabled(True) @@ -134,19 +83,6 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self.repoLabel.setText(self.tr('Repository URL:')) self.is_remote_repo = True - # No need to add this function to JobsManager because repo is set for the first time - def run(self): - if self.validate() and self.password_listener(): - params = BorgInitJob.prepare(self.values) - if params['ok']: - self.saveButton.setEnabled(False) - job = BorgInitJob(params['cmd'], params) - job.updated.connect(self._set_status) - job.result.connect(self.run_result) - QApplication.instance().jobs_manager.add_job(job) - else: - self._set_status(params['message']) - def _set_status(self, text): self.errorText.setText(text) self.errorText.repaint() @@ -159,6 +95,73 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): else: self._set_status(self.tr('Unable to add your repository.')) + def init_ssh_key(self): + keys = get_private_keys() + for key in keys: + self.sshComboBox.addItem(f'{key}', key) + + def validate(self): + """Pre-flight check for valid input and borg binary.""" + if self.is_remote_repo and not re.match(r'.+:.+', self.values['repo_url']): + self._set_status(self.tr('Please enter a valid repo URL or select a local path.')) + return False + + if len(self.values['repo_name']) > 64: + self._set_status(self.tr('Repository name must be less than 64 characters.')) + return False + + if RepoModel.get_or_none(RepoModel.url == self.values['repo_url']) is not None: + self._set_status(self.tr('This repo has already been added.')) + return False + + return True + + @property + def values(self): + out = dict( + ssh_key=self.sshComboBox.currentData(), + repo_url=self.repoURL.text(), + repo_name=self.repoName.text(), + password=self.passwordInput.get_password(), + extra_borg_arguments=self.extraBorgArgumentsLineEdit.text(), + ) + return out + + +class AddRepoWindow(RepoWindow): + def __init__(self, parent=None): + super().__init__(parent) + + self.passwordInput = PasswordInput() + self.passwordInput.add_form_to_layout(self.repoDataFormLayout) + + self.encryptionLabel = QLabel(self.tr('Encryption:')) + self.encryptionComboBox = QComboBox() + self.encryptionComboBox.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + + self.advancedFormLayout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.encryptionLabel) + self.advancedFormLayout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.encryptionComboBox) + + self.encryptionComboBox.activated.connect(self.display_backend_warning) + self.encryptionComboBox.currentIndexChanged.connect(self.encryption_listener) + + self.display_backend_warning() + self.init_encryption() + + def set_password(self, URL): + '''Autofill password from keyring only if current entry is empty''' + password = VortaKeyring.get_keyring().get_password('vorta-repo', URL) + if password and self.passwordInput.get_password() == "": + self.passwordInput.set_error_label(self.tr("Autofilled password from password manager.")) + self.passwordInput.passwordLineEdit.setText(password) + self.passwordInput.confirmLineEdit.setText(password) + + @property + def values(self): + out = super().values + out['encryption'] = self.encryptionComboBox.currentData() + return out + def init_encryption(self): if borg_compat.check('V2'): encryption_algos = [ @@ -191,64 +194,49 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self.encryptionComboBox.model().item(2).setEnabled(False) self.encryptionComboBox.setCurrentIndex(1) - def init_ssh_key(self): - keys = get_private_keys() - for key in keys: - self.sshComboBox.addItem(f'{key}', key) - - def validate(self): - """Pre-flight check for valid input and borg binary.""" - if self.is_remote_repo and not re.match(r'.+:.+', self.values['repo_url']): - self._set_status(self.tr('Please enter a valid repo URL or select a local path.')) - return False - - if len(self.values['repo_name']) > 64: - self._set_status(self.tr('Repository name must be less than 64 characters.')) - return False - - if RepoModel.get_or_none(RepoModel.url == self.values['repo_url']) is not None: - self._set_status(self.tr('This repo has already been added.')) - return False - - return True - - def password_listener(self): + def encryption_listener(self): '''Validates passwords only if its going to be used''' if self.values['encryption'] == 'none': - self.passwordLabel.setText("") - return True + self.passwordInput.set_validation_enabled(False) else: - firstPass = self.passwordLineEdit.text() - secondPass = self.confirmLineEdit.text() - msg = validate_passwords(firstPass, secondPass) - self.passwordLabel.setText(translate('utils', msg)) - return not bool(msg) + self.passwordInput.set_validation_enabled(True) + + def display_backend_warning(self): + '''Display password backend message based off current keyring''' + if self.encryptionComboBox.currentData() != 'none': + self.passwordInput.set_error_label(VortaKeyring.get_keyring().get_backend_warning()) + + def validate(self): + return super().validate() and self.passwordInput.validate() + + def run(self): + if self.validate(): + params = BorgInitJob.prepare(self.values) + if params['ok']: + self.saveButton.setEnabled(False) + job = BorgInitJob(params['cmd'], params) + job.updated.connect(self._set_status) + job.result.connect(self.run_result) + QApplication.instance().jobs_manager.add_job(job) + else: + self._set_status(params['message']) -class ExistingRepoWindow(AddRepoWindow): +class ExistingRepoWindow(RepoWindow): def __init__(self): super().__init__() - self.encryptionComboBox.hide() - self.encryptionLabel.hide() self.title.setText(self.tr('Connect to existing Repository')) - self.showHideAction.setText(self.tr("Show my password")) - self.passwordLineEdit.textChanged.disconnect() - self.confirmLineEdit.textChanged.disconnect() - self.confirmLineEdit.hide() - self.confirmLabel.hide() - del self.confirmLineEdit - del self.confirmLabel - def set_visibility(self, visible): - visibility = QLineEdit.EchoMode.Normal if visible else QLineEdit.EchoMode.Password - self.passwordLineEdit.setEchoMode(visibility) + self.passwordLabel = QLabel(self.tr('Password:')) + self.passwordInput = PasswordLineEdit() + self.repoDataFormLayout.addRow(self.passwordLabel, self.passwordInput) - if visible: - self.showHideAction.setIcon(get_colored_icon("eye-slash")) - self.showHideAction.setText(self.tr("Hide my password")) - else: - self.showHideAction.setIcon(get_colored_icon("eye")) - self.showHideAction.setText(self.tr("Show my password")) + def set_password(self, URL): + '''Autofill password from keyring only if current entry is empty''' + password = VortaKeyring.get_keyring().get_password('vorta-repo', URL) + if password and self.passwordInput.get_password() == "": + self._set_status(self.tr("Autofilled password from password manager.")) + self.passwordInput.setText(password) def run(self): if self.validate(): diff --git a/tests/test_password_input.py b/tests/test_password_input.py new file mode 100644 index 00000000..429b99e4 --- /dev/null +++ b/tests/test_password_input.py @@ -0,0 +1,165 @@ +import pytest +from PyQt6.QtWidgets import QFormLayout, QWidget +from vorta.views.partials.password_input import PasswordInput, PasswordLineEdit + + +def test_create_password_line_edit(qtbot): + password_line_edit = PasswordLineEdit() + qtbot.addWidget(password_line_edit) + assert password_line_edit is not None + + +def test_password_line_get_password(qtbot): + password_line_edit = PasswordLineEdit() + qtbot.addWidget(password_line_edit) + + assert password_line_edit.get_password() == "" + + qtbot.keyClicks(password_line_edit, "test") + assert password_line_edit.get_password() == "test" + + +def test_password_line_visible(qtbot): + password_line_edit = PasswordLineEdit() + qtbot.addWidget(password_line_edit) + assert not password_line_edit.visible + + password_line_edit.toggle_visibility() + assert password_line_edit.visible + + with pytest.raises(TypeError): + password_line_edit.visible = "OK" + + +def test_password_line_error_state(qtbot): + password_line_edit = PasswordLineEdit() + qtbot.addWidget(password_line_edit) + assert not password_line_edit.error_state + assert password_line_edit.styleSheet() == "" + + password_line_edit.error_state = True + assert password_line_edit.error_state + assert password_line_edit.styleSheet() == "QLineEdit { border: 2px solid red; }" + + +def test_password_line_visibility_button(qtbot): + password_line_edit = PasswordLineEdit(show_visibility_button=False) + qtbot.addWidget(password_line_edit) + assert not password_line_edit._show_visibility_button + + password_line_edit = PasswordLineEdit() + qtbot.addWidget(password_line_edit) + assert password_line_edit._show_visibility_button + + # test visibility button + password_line_edit.showHideAction.trigger() + assert password_line_edit.visible + password_line_edit.showHideAction.trigger() + assert not password_line_edit.visible + + +# PasswordInput +def test_create_password_input(qapp, qtbot): + password_input = PasswordInput() + qtbot.addWidget(password_input.create_form_widget(parent=qapp.main_window)) + assert password_input is not None + + assert not password_input.passwordLineEdit.error_state + assert not password_input.confirmLineEdit.error_state + + +def test_password_input_get_password(qapp, qtbot): + password_input = PasswordInput() + qtbot.addWidget(password_input.create_form_widget(parent=qapp.main_window)) + + assert password_input.get_password() == "" + + password_input.passwordLineEdit.setText("test") + assert password_input.get_password() == "test" + + +def test_password_input_validation(qapp, qtbot): + password_input = PasswordInput(minimum_length=10) + qtbot.addWidget(password_input.create_form_widget(parent=qapp.main_window)) + + qtbot.keyClicks(password_input.passwordLineEdit, "123456789") + qtbot.keyClicks(password_input.confirmLineEdit, "123456789") + + assert password_input.passwordLineEdit.error_state + assert password_input.validation_label.text() == "Passwords must be atleast 10 characters long." + + password_input.clear() + qtbot.keyClicks(password_input.passwordLineEdit, "123456789") + qtbot.keyClicks(password_input.confirmLineEdit, "test") + + assert password_input.passwordLineEdit.error_state + assert password_input.confirmLineEdit.error_state + assert password_input.validation_label.text() == "Passwords must be identical and atleast 10 characters long." + + password_input.clear() + qtbot.keyClicks(password_input.passwordLineEdit, "1234567890") + qtbot.keyClicks(password_input.confirmLineEdit, "test") + + assert not password_input.passwordLineEdit.error_state + assert password_input.confirmLineEdit.error_state + assert password_input.validation_label.text() == "Passwords must be identical." + + password_input.clear() + qtbot.keyClicks(password_input.passwordLineEdit, "1234567890") + qtbot.keyClicks(password_input.confirmLineEdit, "1234567890") + + assert not password_input.passwordLineEdit.error_state + assert not password_input.confirmLineEdit.error_state + assert password_input.validation_label.text() == "" + + +def test_password_input_validation_disabled(qapp, qtbot): + password_input = PasswordInput(show_error=False) + qtbot.addWidget(password_input.create_form_widget(parent=qapp.main_window)) + + qtbot.keyClicks(password_input.passwordLineEdit, "test") + qtbot.keyClicks(password_input.confirmLineEdit, "test") + + assert not password_input.passwordLineEdit.error_state + assert not password_input.confirmLineEdit.error_state + assert password_input.validation_label.text() == "" + + password_input.set_validation_enabled(True) + qtbot.keyClicks(password_input.passwordLineEdit, "s") + qtbot.keyClicks(password_input.confirmLineEdit, "a") + + assert password_input.passwordLineEdit.error_state + assert password_input.confirmLineEdit.error_state + assert password_input.validation_label.text() == "Passwords must be identical and atleast 9 characters long." + + password_input.set_validation_enabled(False) + assert not password_input.passwordLineEdit.error_state + assert not password_input.confirmLineEdit.error_state + assert password_input.validation_label.text() == "" + + +def test_password_input_set_label(qapp, qtbot): + password_input = PasswordInput(label=["test", "test2"]) + qtbot.addWidget(password_input.create_form_widget(parent=qapp.main_window)) + + assert password_input._label_password.text() == "test" + assert password_input._label_confirm.text() == "test2" + + password_input.set_labels("test3", "test4") + assert password_input._label_password.text() == "test3" + assert password_input._label_confirm.text() == "test4" + + +def test_password_input_add_form_to_layout(qapp, qtbot): + password_input = PasswordInput() + + widget = QWidget() + form_layout = QFormLayout(widget) + + qtbot.addWidget(widget) + password_input.add_form_to_layout(form_layout) + + assert form_layout.itemAt(0, QFormLayout.ItemRole.LabelRole).widget() == password_input._label_password + assert form_layout.itemAt(0, QFormLayout.ItemRole.FieldRole).widget() == password_input.passwordLineEdit + assert form_layout.itemAt(1, QFormLayout.ItemRole.LabelRole).widget() == password_input._label_confirm + assert form_layout.itemAt(1, QFormLayout.ItemRole.FieldRole).widget() == password_input.confirmLineEdit diff --git a/tests/test_repo.py b/tests/test_repo.py index b5b07096..3e13084d 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -19,33 +19,36 @@ def test_repo_add_failures(qapp, qtbot, mocker, borg_json_output): add_repo_window = main.repoTab._window qtbot.addWidget(add_repo_window) - qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD) - qtbot.keyClicks(add_repo_window.confirmLineEdit, LONG_PASSWORD) + qtbot.keyClicks(add_repo_window.passwordInput.passwordLineEdit, LONG_PASSWORD) + qtbot.keyClicks(add_repo_window.passwordInput.confirmLineEdit, LONG_PASSWORD) qtbot.keyClicks(add_repo_window.repoURL, 'aaa') qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) assert add_repo_window.errorText.text().startswith('Please enter a valid') - add_repo_window.passwordLineEdit.clear() - add_repo_window.confirmLineEdit.clear() - qtbot.keyClicks(add_repo_window.passwordLineEdit, SHORT_PASSWORD) - qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD) + add_repo_window.passwordInput.passwordLineEdit.clear() + add_repo_window.passwordInput.confirmLineEdit.clear() + qtbot.keyClicks(add_repo_window.passwordInput.passwordLineEdit, SHORT_PASSWORD) + qtbot.keyClicks(add_repo_window.passwordInput.confirmLineEdit, SHORT_PASSWORD) qtbot.keyClicks(add_repo_window.repoURL, 'bbb.com:repo') qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) - assert add_repo_window.passwordLabel.text() == 'Passwords must be greater than 8 characters long.' + assert add_repo_window.passwordInput.validation_label.text() == 'Passwords must be atleast 9 characters long.' - add_repo_window.passwordLineEdit.clear() - add_repo_window.confirmLineEdit.clear() - qtbot.keyClicks(add_repo_window.passwordLineEdit, SHORT_PASSWORD + "1") - qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD) + add_repo_window.passwordInput.passwordLineEdit.clear() + add_repo_window.passwordInput.confirmLineEdit.clear() + qtbot.keyClicks(add_repo_window.passwordInput.passwordLineEdit, SHORT_PASSWORD + "1") + qtbot.keyClicks(add_repo_window.passwordInput.confirmLineEdit, SHORT_PASSWORD) qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) - assert add_repo_window.passwordLabel.text() == 'Passwords must be identical and greater than 8 characters long.' + assert ( + add_repo_window.passwordInput.validation_label.text() + == 'Passwords must be identical and atleast 9 characters long.' + ) - add_repo_window.passwordLineEdit.clear() - add_repo_window.confirmLineEdit.clear() - qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD) - qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD) + add_repo_window.passwordInput.passwordLineEdit.clear() + add_repo_window.passwordInput.confirmLineEdit.clear() + qtbot.keyClicks(add_repo_window.passwordInput.passwordLineEdit, LONG_PASSWORD) + qtbot.keyClicks(add_repo_window.passwordInput.confirmLineEdit, SHORT_PASSWORD) qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.MouseButton.LeftButton) - assert add_repo_window.passwordLabel.text() == 'Passwords must be identical.' + assert add_repo_window.passwordInput.validation_label.text() == 'Passwords must be identical.' def test_repo_unlink(qapp, qtbot, monkeypatch): @@ -76,7 +79,7 @@ def test_password_autofill(qapp, qtbot): qtbot.keyClicks(add_repo_window.repoURL, test_repo_url) - assert add_repo_window.passwordLineEdit.text() == password + assert add_repo_window.passwordInput.passwordLineEdit.text() == password def test_repo_add_success(qapp, qtbot, mocker, borg_json_output): @@ -89,8 +92,8 @@ def test_repo_add_success(qapp, qtbot, mocker, borg_json_output): qtbot.keyClicks(add_repo_window.repoURL, test_repo_url) qtbot.keyClicks(add_repo_window.repoName, test_repo_name) - qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD) - qtbot.keyClicks(add_repo_window.confirmLineEdit, LONG_PASSWORD) + qtbot.keyClicks(add_repo_window.passwordInput.passwordLineEdit, LONG_PASSWORD) + qtbot.keyClicks(add_repo_window.passwordInput.confirmLineEdit, LONG_PASSWORD) stdout, stderr = borg_json_output('info') popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)