/ src / qt / test / addressbooktests.cpp
addressbooktests.cpp
  1  // Copyright (c) 2017-present The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4  
  5  #include <qt/test/addressbooktests.h>
  6  #include <qt/test/util.h>
  7  #include <test/util/setup_common.h>
  8  
  9  #include <interfaces/chain.h>
 10  #include <interfaces/node.h>
 11  #include <qt/addressbookpage.h>
 12  #include <qt/clientmodel.h>
 13  #include <qt/editaddressdialog.h>
 14  #include <qt/optionsmodel.h>
 15  #include <qt/platformstyle.h>
 16  #include <qt/qvalidatedlineedit.h>
 17  #include <qt/walletmodel.h>
 18  
 19  #include <key.h>
 20  #include <key_io.h>
 21  #include <wallet/wallet.h>
 22  #include <wallet/test/util.h>
 23  #include <walletinitinterface.h>
 24  
 25  #include <chrono>
 26  
 27  #include <QApplication>
 28  #include <QLineEdit>
 29  #include <QMessageBox>
 30  #include <QTableView>
 31  #include <QTimer>
 32  
 33  using wallet::AddWallet;
 34  using wallet::CWallet;
 35  using wallet::CreateMockableWalletDatabase;
 36  using wallet::RemoveWallet;
 37  using wallet::WALLET_FLAG_DESCRIPTORS;
 38  using wallet::WalletContext;
 39  
 40  namespace
 41  {
 42  
 43  /**
 44   * Fill the edit address dialog box with data, submit it, and ensure that
 45   * the resulting message meets expectations.
 46   */
 47  void EditAddressAndSubmit(
 48          EditAddressDialog* dialog,
 49          const QString& label, const QString& address, QString expected_msg)
 50  {
 51      QString warning_text;
 52  
 53      dialog->findChild<QLineEdit*>("labelEdit")->setText(label);
 54      dialog->findChild<QValidatedLineEdit*>("addressEdit")->setText(address);
 55  
 56      ConfirmMessage(&warning_text, 5ms);
 57      dialog->accept();
 58      QCOMPARE(warning_text, expected_msg);
 59  }
 60  
 61  /**
 62   * Test adding various send addresses to the address book.
 63   *
 64   * There are three cases tested:
 65   *
 66   *   - new_address: a new address which should add as a send address successfully.
 67   *   - existing_s_address: an existing sending address which won't add successfully.
 68   *   - existing_r_address: an existing receiving address which won't add successfully.
 69   *
 70   * In each case, verify the resulting state of the address book and optionally
 71   * the warning message presented to the user.
 72   */
 73  void TestAddAddressesToSendBook(interfaces::Node& node)
 74  {
 75      TestChain100Setup test;
 76      auto wallet_loader = interfaces::MakeWalletLoader(*test.m_node.chain, *Assert(test.m_node.args));
 77      test.m_node.wallet_loader = wallet_loader.get();
 78      node.setContext(&test.m_node);
 79      const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockableWalletDatabase());
 80      wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
 81      {
 82          LOCK(wallet->cs_wallet);
 83          wallet->SetupDescriptorScriptPubKeyMans();
 84      }
 85  
 86      auto build_address{[]() {
 87          const WitnessV0KeyHash dest{GenerateRandomKey().GetPubKey()};
 88          return std::make_pair(dest, QString::fromStdString(EncodeDestination(dest)));
 89      }};
 90  
 91      CTxDestination r_key_dest, s_key_dest;
 92  
 93      // Add a preexisting "receive" entry in the address book.
 94      QString preexisting_r_address;
 95      QString r_label("already here (r)");
 96  
 97      // Add a preexisting "send" entry in the address book.
 98      QString preexisting_s_address;
 99      QString s_label("already here (s)");
100  
101      // Define a new address (which should add to the address book successfully).
102      QString new_address_a;
103      QString new_address_b;
104  
105      std::tie(r_key_dest, preexisting_r_address) = build_address();
106      std::tie(s_key_dest, preexisting_s_address) = build_address();
107      std::tie(std::ignore, new_address_a) = build_address();
108      std::tie(std::ignore, new_address_b) = build_address();
109  
110      {
111          LOCK(wallet->cs_wallet);
112          wallet->SetAddressBook(r_key_dest, r_label.toStdString(), wallet::AddressPurpose::RECEIVE);
113          wallet->SetAddressBook(s_key_dest, s_label.toStdString(), wallet::AddressPurpose::SEND);
114      }
115  
116      auto check_addbook_size = [&wallet](int expected_size) {
117          LOCK(wallet->cs_wallet);
118          QCOMPARE(static_cast<int>(wallet->m_address_book.size()), expected_size);
119      };
120  
121      // We should start with the two addresses we added earlier and nothing else.
122      check_addbook_size(2);
123  
124      // Initialize relevant QT models.
125      std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
126      OptionsModel optionsModel(node);
127      bilingual_str error;
128      QVERIFY(optionsModel.Init(error));
129      ClientModel clientModel(node, &optionsModel);
130      WalletContext& context = *node.walletLoader().context();
131      AddWallet(context, wallet);
132      WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel, platformStyle.get());
133      RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt);
134      EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress);
135      editAddressDialog.setModel(walletModel.getAddressTableModel());
136  
137      AddressBookPage address_book{platformStyle.get(), AddressBookPage::ForEditing, AddressBookPage::SendingTab};
138      address_book.setModel(walletModel.getAddressTableModel());
139      auto table_view = address_book.findChild<QTableView*>("tableView");
140      QCOMPARE(table_view->model()->rowCount(), 1);
141  
142      EditAddressAndSubmit(
143          &editAddressDialog, QString("uhoh"), preexisting_r_address,
144          QString(
145              "Address \"%1\" already exists as a receiving address with label "
146              "\"%2\" and so cannot be added as a sending address."
147              ).arg(preexisting_r_address).arg(r_label));
148      check_addbook_size(2);
149      QCOMPARE(table_view->model()->rowCount(), 1);
150  
151      EditAddressAndSubmit(
152          &editAddressDialog, QString("uhoh, different"), preexisting_s_address,
153          QString(
154              "The entered address \"%1\" is already in the address book with "
155              "label \"%2\"."
156              ).arg(preexisting_s_address).arg(s_label));
157      check_addbook_size(2);
158      QCOMPARE(table_view->model()->rowCount(), 1);
159  
160      // Submit a new address which should add successfully - we expect the
161      // warning message to be blank.
162      EditAddressAndSubmit(
163          &editAddressDialog, QString("io - new A"), new_address_a, QString(""));
164      check_addbook_size(3);
165      QCOMPARE(table_view->model()->rowCount(), 2);
166  
167      EditAddressAndSubmit(
168          &editAddressDialog, QString("io - new B"), new_address_b, QString(""));
169      check_addbook_size(4);
170      QCOMPARE(table_view->model()->rowCount(), 3);
171  
172      auto search_line = address_book.findChild<QLineEdit*>("searchLineEdit");
173  
174      search_line->setText(r_label);
175      QCOMPARE(table_view->model()->rowCount(), 0);
176  
177      search_line->setText(s_label);
178      QCOMPARE(table_view->model()->rowCount(), 1);
179  
180      search_line->setText("io");
181      QCOMPARE(table_view->model()->rowCount(), 2);
182  
183      // Check wildcard "?".
184      search_line->setText("io?new");
185      QCOMPARE(table_view->model()->rowCount(), 0);
186      search_line->setText("io???new");
187      QCOMPARE(table_view->model()->rowCount(), 2);
188  
189      // Check wildcard "*".
190      search_line->setText("io*new");
191      QCOMPARE(table_view->model()->rowCount(), 2);
192      search_line->setText("*");
193      QCOMPARE(table_view->model()->rowCount(), 3);
194  
195      search_line->setText(preexisting_r_address);
196      QCOMPARE(table_view->model()->rowCount(), 0);
197  
198      search_line->setText(preexisting_s_address);
199      QCOMPARE(table_view->model()->rowCount(), 1);
200  
201      search_line->setText(new_address_a);
202      QCOMPARE(table_view->model()->rowCount(), 1);
203  
204      search_line->setText(new_address_b);
205      QCOMPARE(table_view->model()->rowCount(), 1);
206  
207      search_line->setText("");
208      QCOMPARE(table_view->model()->rowCount(), 3);
209  }
210  
211  } // namespace
212  
213  void AddressBookTests::addressBookTests()
214  {
215  #ifdef Q_OS_MACOS
216      if (QApplication::platformName() == "minimal") {
217          // Disable for mac on "minimal" platform to avoid crashes inside the Qt
218          // framework when it tries to look up unimplemented cocoa functions,
219          // and fails to handle returned nulls
220          // (https://bugreports.qt.io/browse/QTBUG-49686).
221          qWarning() << "Skipping AddressBookTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke "
222                        "with 'QT_QPA_PLATFORM=cocoa test_bitcoin-qt' on mac, or else use a linux or windows build.";
223          return;
224      }
225  #endif
226      TestAddAddressesToSendBook(m_node);
227  }