def test_change_nickname(): """Change the nickname and press cancel.""" # Given: form = EditController() form.start() # When we edit the nickname: form.ui.lineedit_nickname.setText("junk") cancel_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Cancel) QTest.mouseClick(cancel_button, Qt.LeftButton) # Then dirty-flag is set: assert form.dirty_flag
def test_do_nothing(): """Just create the form and press ok.""" # Given: form = EditController() form.start() # When we do nothing: ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok) QTest.mouseClick(ok_button, Qt.LeftButton) # Then check defaults and form returns 1 because we ok'ed: assert not form.dirty_flag assert not form.update_flag assert form.result() == 1
def test_base_and_special_chars(): """Verify that base == 64 implies special-chars.""" # Given: form = EditController() form.start() # When we set the base to 64: form.ui.combobox_base.setCurrentText("64") # Then we must have special chars: assert form.ui.checkbox_special_char.isChecked() == True # When we unset special chars: form.ui.checkbox_special_char.setChecked(False) # Then the base must revert to 32: assert form.ui.combobox_base.currentText() == "32"
def test_toggle_special_chars(): """Toggle the special-chars checkbox and press cancel.""" # Given: form = EditController() form.start() # When we toggle the special-chars checkbox: form.ui.checkbox_special_char.setChecked(True) cancel_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Cancel) QTest.mouseClick(cancel_button, Qt.LeftButton) # Then dirty-flag is set and form returns 0 b/c we cancelled: assert form.dirty_flag assert not form.update_flag assert form.result() == 0
def test_update(): """Update an existing password.""" # Given: nick, user, host, iteration, hint = "nick", "user", "host.com", 3, "hint" orig_password = Password(nick, user, host, iteration=iteration, hint=hint) new_nick, new_user = "******", "new_user" form = EditController() form.start() form.populate_form_from_password(orig_password) # Skip the "Are you sure?" messagebox: form.ui.edit_buttons.accepted.disconnect(form.confirm_accept) form.ui.edit_buttons.accepted.connect(form.accept) # When we update the original password: form.ui.lineedit_nickname.setText(new_nick) form.ui.lineedit_username.setText(new_user) ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok) QTest.mouseClick(ok_button, Qt.LeftButton) password = form.create_password_from_form() # Then we get those updates back: assert form.dirty_flag assert form.update_flag assert form.result() == 1 assert password == Password(new_nick, new_user, host, iteration=iteration, hint=hint)
def test_create(): """Create a new password.""" # Given: form = EditController() form.start() nick, user, host, iteration, hint = "nick", "user", "host.com", 3, "hint" # When we create a new pw: form.ui.lineedit_nickname.setText(nick) form.ui.lineedit_username.setText(user) form.ui.lineedit_hostname.setText(host) form.ui.spinbox_iteration.setValue(iteration) form.ui.lineedit_hint.setText(hint) ok_button = form.ui.edit_buttons.button(form.ui.edit_buttons.Ok) QTest.mouseClick(ok_button, Qt.LeftButton) password = form.create_password_from_form() # Then we get that new pw back: assert form.dirty_flag assert not form.update_flag assert form.result() == 1 assert password == Password(nickname=nick, username=user, hostname=host, iteration=iteration, hint=hint)
def __init__(self, app): super().__init__() self.app = app self.ui = Ui_main_form() self.edit_form = EditController() self.pass_db, self.passwords_dic = None, None
class MainController(QtWidgets.QDialog): """Handle communication between the main Qt form and the database.""" def __init__(self, app): super().__init__() self.app = app self.ui = Ui_main_form() self.edit_form = EditController() self.pass_db, self.passwords_dic = None, None def start(self, pass_db, pass_dic): """Real initialization: ui, passwords-list, connect callbacks""" self.ui.setupUi(self) self.edit_form.start() self._connect_callbacks() self.pass_db, self.passwords_dic = pass_db, pass_dic self._populate_pw_nicknames_list() @staticmethod def create(app, pass_db=None, pass_dic=None, db_path=None): """We're given either (in prod) a db_path that we use to get a pass_db and pass_dic, or (in test) a pre-built pass_db and pass_dic.""" main_form = MainController(app) if db_path is not None: pass_db, pass_dic = main_form._get_data(db_path) main_form.start(pass_db, pass_dic) return main_form def _get_data(self, db_path): """Create communications methods for the back-end get_data and call it.""" def ask_to_create_new(): """Ask user if we want to create a new password db.""" msg_box = QtWidgets.QMessageBox() msg_box.setFont(self.font()) msg_box.setText("Password database doesn't already exist.") msg_box.setInformativeText("Create a new one?") msg_box.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) response = msg_box.exec_() return response == QtWidgets.QMessageBox.Yes def error_getting_db(): """Notify user of error getting password database.""" msg_box = QtWidgets.QMessageBox() msg_box.setFont(self.font()) msg_box.setText("Error opening passwords db. Exiting.") msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) msg_box.exec_() return PasswordDB.get_data(ask_to_create_new, error_getting_db, db_path) def _populate_pw_nicknames_list(self): """Populate the main drop-down containing all password nicknames. According to how many items there are, decide which buttons on the form need to be enabled. """ def _set_widgets_enabled(state): """Enable or disable widgets depending on whether we have data yet.""" for widget in [ self.ui.combobox_password_nicknames, self.ui.lineedit_enter_proto, self.ui.button_get, self.ui.button_hint, self.ui.button_update, self.ui.button_delete ]: widget.setEnabled(state) nicknames = sorted(self.passwords_dic.keys()) self.ui.combobox_password_nicknames.clear() if len(nicknames) > 1: # add header and nicknames self.ui.combobox_password_nicknames.addItems( [_NICKNAMES_LIST_HEADER]) self.ui.combobox_password_nicknames.addItems(nicknames) if nicknames: # have passwords, can do stuff with them _set_widgets_enabled(True) self.ui.combobox_password_nicknames.setFocus() else: # no passwords yet, can only create new _set_widgets_enabled(False) self.ui.button_new.setFocus() def _connect_callbacks(self): """Connect ui events to callbacks.""" self.ui.lineedit_enter_proto.returnPressed.connect(self.get_password) self.ui.checkbox_show_password.stateChanged.connect( self.toggle_show_password) self.ui.combobox_password_nicknames.currentIndexChanged.connect( self._clear_both) self.ui.button_get.clicked.connect(self.get_password) self.ui.button_hint.clicked.connect(self.get_hint) self.ui.button_new.clicked.connect(self.create_or_update_password) self.ui.button_update.clicked.connect(self.create_or_update_password) self.ui.button_delete.clicked.connect(self.delete_password) def _get_selected(self): """Figure out which password-nick they selected.""" selected_text = str(self.ui.combobox_password_nicknames.currentText()) if selected_text != _NICKNAMES_LIST_HEADER: return (self.ui.combobox_password_nicknames.currentIndex(), selected_text) return (0, None) # basic interactions with the main window: def _display_message(self, line_1, line_2): """Display the given message, lines 1 and 2.""" self.ui.label_resp_header.setText(line_1) self.ui.label_resp_body.setText(line_2) def _display_error(self, error_message): """Display the given error.""" self._display_message("<font color=red>ERROR</font>", "<font color=red>" + error_message + "</font>") def _clear_proto_passwords(self): """Clear the line where user enters proto-password.""" self.ui.lineedit_enter_proto.setText("") def _clear_display(self): """Clear the main display line.""" self._display_message('<font size="48"> </font>', "") def _clear_both(self): """Clear both the user-entry line and the main display line.""" self._clear_proto_passwords() self._clear_display() def get_password(self): """Get password for a particular selection.""" self._clear_display() # get the selection: _, selection = self._get_selected() if not selection: self._display_error(_SELECTION_ERROR) return proto_pw_1 = str(self.ui.lineedit_enter_proto.text()) password = self.passwords_dic[selection].calculate_password(proto_pw_1) self._display_message("Password copied to clipboard:", password) self.app.clipboard().setText(password) self.ui.combobox_password_nicknames.setFocus() def get_hint(self): """Look up the stored hint and display it.""" # Get the selection: _, selection = self._get_selected() if not selection: self._display_error(_SELECTION_ERROR) return # Get the hint, display it, and go to entry line-edit self._display_message("Hint is:", self.passwords_dic[selection].hint) self.ui.lineedit_enter_proto.setFocus() self.ui.lineedit_enter_proto.selectAll() def create_or_update_password(self): """ Use same form either to create a new password or to update an existing password. """ def get_password_from_user(is_update): """Create an entry form, validate user input, and return a Password object.""" # If we're updating then populate the entry form with existing data to update: _, orig_nickname = self._get_selected() orig_pass = self.passwords_dic.get(orig_nickname, None) if is_update: if orig_pass: self.edit_form.populate_form_from_password(orig_pass) else: self._display_error(_SELECTION_ERROR) return None, None else: self.edit_form.clear() # We'll keep showing the create-new-password window until we get good data. # (Unless the user cancels, in which case of course quit.) bad_data_entered = True # Human-entered data is bad until we check it while bad_data_entered: # If the user cancels then quit. self.edit_form.ui.lineedit_nickname.setFocus() edit_return = self.edit_form.exec_() if not edit_return: return None, None # Check for valid data: can't have either a blank nickname or one that already exists. nickname = str(self.edit_form.ui.lineedit_nickname.text()) nickname_is_blank = nickname == '' nickname_already_exists = nickname in self.passwords_dic bad_data_entered = nickname_is_blank or ( nickname_already_exists and not is_update) if bad_data_entered: msg_box = QtWidgets.QMessageBox() if nickname_is_blank: msg_box.setText( 'Nickname cannot be blank. Please try again.') if nickname_already_exists and not is_update: msg_box.setText( nickname + ' already exists in password database. Please try again.' ) msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) msg_box.setFont(self.font()) msg_box.exec_() return orig_nickname, self.edit_form.create_password_from_form() is_update = self.sender() == self.ui.button_update orig_nickname, password = get_password_from_user(is_update) if password is None: return # Either update the data-store or create a new password. if is_update: self.pass_db.update_old_password(orig_nickname, password) del self.passwords_dic[orig_nickname] else: self.pass_db.create_new_password(password) self.passwords_dic[password.nickname] = password self._populate_pw_nicknames_list() self._clear_both() return def delete_password(self, confirmed=False): """Respond to a request to delete a particular password. confirmed (default False) is a hack to allow testing without a confirmation messagebox. """ def confirm_deletion(selection): """Confirm whether we want to delete.""" msg_box = QtWidgets.QMessageBox() msg_box.setText('About to delete password "' + selection + '".') msg_box.setInformativeText('Are you sure?') msg_box.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) msg_box.setFont(self.font()) response = msg_box.exec_() return response == QtWidgets.QMessageBox.Yes # Clear the display: self._clear_both() # Get the selection: _, selection = self._get_selected() if not selection: self._display_error(_SELECTION_ERROR) return # Confirm and delete: if confirmed or confirm_deletion(selection): self.pass_db.delete_password(selection) del self.passwords_dic[selection] self._populate_pw_nicknames_list() self._display_message("STATUS:", selection + " has been deleted.") def reject(self): """User is dismissing the form. Close up shop before closing app.""" self.app.clipboard().setText('') self.pass_db.close_db() super().reject() def toggle_show_password(self): """Toggle whether or to display the password as it's typed.""" if self.ui.checkbox_show_password.isChecked(): self.ui.lineedit_enter_proto.setEchoMode( QtWidgets.QLineEdit.Normal) else: self.ui.lineedit_enter_proto.setEchoMode( QtWidgets.QLineEdit.Password)