Skip to content

Commit 25ad2f7

Browse files
committed
Merge #12830: [qt] [tests] Clarify address book error messages, add tests
5109fc4 [tests] [qt] Add tests for address book manipulation via EditAddressDialog (James O'Beirne) 9c01be1 [tests] [qt] Introduce qt/test/util with a generalized ConfirmMessage (James O'Beirne) 8cdcaee [qt] Display more helpful message when adding a send address has failed (James O'Beirne) c5b2770 Add purpose arg to Wallet::getAddress (James O'Beirne) Pull request description: Addresses bitcoin/bitcoin#12796. When a user attempts to add to the address book a sending address which is already present as a receiving address, they're presented with a confusing error indicating the address is already present in the book, despite the fact that this row is currently invisible. ![selection_011](https://user-images.githubusercontent.com/73197/38096704-8a2948d2-3341-11e8-9632-7d563201f28c.jpg) This change adds a more specific error message indicating its existence as a receiving address (as discussed in the linked issue). ![selection_016](https://user-images.githubusercontent.com/73197/38198467-fa26164e-365a-11e8-8fc5-ddab9caf2fbd.jpg) This change also adds some tests exercising use of the address book via QT. Adding so much test code for such a trivial change may seem weird, but it's my hope that this will make further test-writing for address book usage (and other QT features) more approachable. Tree-SHA512: fbdd5564f7a9a2380bbe437f3378e8d4d5fd9201efff4879b72bc23f2cc1c2eecaf2b811994c25070ee052422e48e47901787c2e62cc584774a997fe6a2a327a
2 parents a785bc3 + 5109fc4 commit 25ad2f7

17 files changed

+279
-40
lines changed

src/Makefile.qttest.include

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ TEST_QT_MOC_CPP = \
1212

1313
if ENABLE_WALLET
1414
TEST_QT_MOC_CPP += \
15+
qt/test/moc_addressbooktests.cpp \
1516
qt/test/moc_paymentservertests.cpp \
1617
qt/test/moc_wallettests.cpp
1718
endif
1819

1920
TEST_QT_H = \
21+
qt/test/addressbooktests.h \
2022
qt/test/compattests.h \
2123
qt/test/rpcnestedtests.h \
2224
qt/test/uritests.h \
25+
qt/test/util.h \
2326
qt/test/paymentrequestdata.h \
2427
qt/test/paymentservertests.h \
2528
qt/test/wallettests.h
@@ -38,11 +41,13 @@ qt_test_test_bitcoin_qt_SOURCES = \
3841
qt/test/rpcnestedtests.cpp \
3942
qt/test/test_main.cpp \
4043
qt/test/uritests.cpp \
44+
qt/test/util.cpp \
4145
$(TEST_QT_H) \
4246
$(TEST_BITCOIN_CPP) \
4347
$(TEST_BITCOIN_H)
4448
if ENABLE_WALLET
4549
qt_test_test_bitcoin_qt_SOURCES += \
50+
qt/test/addressbooktests.cpp \
4651
qt/test/paymentservertests.cpp \
4752
qt/test/wallettests.cpp \
4853
wallet/test/wallet_test_fixture.cpp

src/interfaces/wallet.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,10 @@ class WalletImpl : public Wallet
152152
{
153153
return m_wallet.DelAddressBook(dest);
154154
}
155-
bool getAddress(const CTxDestination& dest, std::string* name, isminetype* is_mine) override
155+
bool getAddress(const CTxDestination& dest,
156+
std::string* name,
157+
isminetype* is_mine,
158+
std::string* purpose) override
156159
{
157160
LOCK(m_wallet.cs_wallet);
158161
auto it = m_wallet.mapAddressBook.find(dest);
@@ -165,6 +168,9 @@ class WalletImpl : public Wallet
165168
if (is_mine) {
166169
*is_mine = IsMine(m_wallet, dest);
167170
}
171+
if (purpose) {
172+
*purpose = it->second.purpose;
173+
}
168174
return true;
169175
}
170176
std::vector<WalletAddress> getAddresses() override

src/interfaces/wallet.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ class Wallet
9999

100100
//! Look up address in wallet, return whether exists.
101101
virtual bool getAddress(const CTxDestination& dest,
102-
std::string* name = nullptr,
103-
isminetype* is_mine = nullptr) = 0;
102+
std::string* name,
103+
isminetype* is_mine,
104+
std::string* purpose) = 0;
104105

105106
//! Get wallet address list.
106107
virtual std::vector<WalletAddress> getAddresses() = 0;

src/qt/addressbookpage.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class AddressBookPage : public QDialog
3838
ForEditing /**< Open address book for editing */
3939
};
4040

41-
explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent);
41+
explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent = 0);
4242
~AddressBookPage();
4343

4444
void setModel(AddressTableModel *model);

src/qt/addresstablemodel.cpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value,
266266
}
267267
// Check for duplicate addresses to prevent accidental deletion of addresses, if you try
268268
// to paste an existing address over another address (with a different label)
269-
if (walletModel->wallet().getAddress(newAddress))
269+
if (walletModel->wallet().getAddress(
270+
newAddress, /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
270271
{
271272
editStatus = DUPLICATE_ADDRESS;
272273
return false;
@@ -351,7 +352,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
351352
}
352353
// Check for duplicate addresses
353354
{
354-
if(walletModel->wallet().getAddress(DecodeDestination(strAddress)))
355+
if (walletModel->wallet().getAddress(
356+
DecodeDestination(strAddress), /* name= */ nullptr, /* is_mine= */ nullptr, /* purpose= */ nullptr))
355357
{
356358
editStatus = DUPLICATE_ADDRESS;
357359
return QString();
@@ -405,21 +407,31 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent
405407
return true;
406408
}
407409

408-
/* Look up label for address in address book, if not found return empty string.
409-
*/
410410
QString AddressTableModel::labelForAddress(const QString &address) const
411411
{
412-
{
413-
CTxDestination destination = DecodeDestination(address.toStdString());
414-
std::string name;
415-
if (walletModel->wallet().getAddress(destination, &name))
416-
{
417-
return QString::fromStdString(name);
418-
}
412+
std::string name;
413+
if (getAddressData(address, &name, /* purpose= */ nullptr)) {
414+
return QString::fromStdString(name);
415+
}
416+
return QString();
417+
}
418+
419+
QString AddressTableModel::purposeForAddress(const QString &address) const
420+
{
421+
std::string purpose;
422+
if (getAddressData(address, /* name= */ nullptr, &purpose)) {
423+
return QString::fromStdString(purpose);
419424
}
420425
return QString();
421426
}
422427

428+
bool AddressTableModel::getAddressData(const QString &address,
429+
std::string* name,
430+
std::string* purpose) const {
431+
CTxDestination destination = DecodeDestination(address.toStdString());
432+
return walletModel->wallet().getAddress(destination, name, /* is_mine= */ nullptr, purpose);
433+
}
434+
423435
int AddressTableModel::lookupAddress(const QString &address) const
424436
{
425437
QModelIndexList lst = match(index(0, Address, QModelIndex()),

src/qt/addresstablemodel.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,12 @@ class AddressTableModel : public QAbstractTableModel
6767
*/
6868
QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type);
6969

70-
/* Look up label for address in address book, if not found return empty string.
71-
*/
70+
/** Look up label for address in address book, if not found return empty string. */
7271
QString labelForAddress(const QString &address) const;
7372

73+
/** Look up purpose for address in address book, if not found return empty string. */
74+
QString purposeForAddress(const QString &address) const;
75+
7476
/* Look up row index of an address in the model.
7577
Return -1 if not found.
7678
*/
@@ -86,6 +88,9 @@ class AddressTableModel : public QAbstractTableModel
8688
QStringList columns;
8789
EditStatus editStatus;
8890

91+
/** Look up address book data given an address string. */
92+
bool getAddressData(const QString &address, std::string* name, std::string* purpose) const;
93+
8994
/** Notify listeners that data changed. */
9095
void emitDataChanged(int index);
9196

src/qt/editaddressdialog.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ void EditAddressDialog::accept()
109109
break;
110110
case AddressTableModel::DUPLICATE_ADDRESS:
111111
QMessageBox::warning(this, windowTitle(),
112-
tr("The entered address \"%1\" is already in the address book.").arg(ui->addressEdit->text()),
112+
getDuplicateAddressWarning(),
113113
QMessageBox::Ok, QMessageBox::Ok);
114114
break;
115115
case AddressTableModel::WALLET_UNLOCK_FAILURE:
@@ -129,6 +129,25 @@ void EditAddressDialog::accept()
129129
QDialog::accept();
130130
}
131131

132+
QString EditAddressDialog::getDuplicateAddressWarning() const
133+
{
134+
QString dup_address = ui->addressEdit->text();
135+
QString existing_label = model->labelForAddress(dup_address);
136+
QString existing_purpose = model->purposeForAddress(dup_address);
137+
138+
if (existing_purpose == "receive" &&
139+
(mode == NewSendingAddress || mode == EditSendingAddress)) {
140+
return tr(
141+
"Address \"%1\" already exists as a receiving address with label "
142+
"\"%2\" and so cannot be added as a sending address."
143+
).arg(dup_address).arg(existing_label);
144+
}
145+
return tr(
146+
"The entered address \"%1\" is already in the address book with "
147+
"label \"%2\"."
148+
).arg(dup_address).arg(existing_label);
149+
}
150+
132151
QString EditAddressDialog::getAddress() const
133152
{
134153
return address;

src/qt/editaddressdialog.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class EditAddressDialog : public QDialog
3030
EditSendingAddress
3131
};
3232

33-
explicit EditAddressDialog(Mode mode, QWidget *parent);
33+
explicit EditAddressDialog(Mode mode, QWidget *parent = 0);
3434
~EditAddressDialog();
3535

3636
void setModel(AddressTableModel *model);
@@ -45,6 +45,9 @@ public Q_SLOTS:
4545
private:
4646
bool saveCurrentRow();
4747

48+
/** Return a descriptive string when adding an already-existing address fails. */
49+
QString getDuplicateAddressWarning() const;
50+
4851
Ui::EditAddressDialog *ui;
4952
QDataWidgetMapper *mapper;
5053
Mode mode;

src/qt/test/addressbooktests.cpp

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#include <qt/test/addressbooktests.h>
2+
#include <qt/test/util.h>
3+
#include <test/test_bitcoin.h>
4+
5+
#include <interfaces/node.h>
6+
#include <qt/addressbookpage.h>
7+
#include <qt/addresstablemodel.h>
8+
#include <qt/editaddressdialog.h>
9+
#include <qt/callback.h>
10+
#include <qt/optionsmodel.h>
11+
#include <qt/platformstyle.h>
12+
#include <qt/qvalidatedlineedit.h>
13+
#include <qt/walletmodel.h>
14+
15+
#include <key.h>
16+
#include <pubkey.h>
17+
#include <key_io.h>
18+
#include <wallet/wallet.h>
19+
20+
#include <QTimer>
21+
#include <QMessageBox>
22+
23+
namespace
24+
{
25+
26+
/**
27+
* Fill the edit address dialog box with data, submit it, and ensure that
28+
* the resulting message meets expectations.
29+
*/
30+
void EditAddressAndSubmit(
31+
EditAddressDialog* dialog,
32+
const QString& label, const QString& address, QString expected_msg)
33+
{
34+
QString warning_text;
35+
36+
dialog->findChild<QLineEdit*>("labelEdit")->setText(label);
37+
dialog->findChild<QValidatedLineEdit*>("addressEdit")->setText(address);
38+
39+
ConfirmMessage(&warning_text, 5);
40+
dialog->accept();
41+
QCOMPARE(warning_text, expected_msg);
42+
}
43+
44+
/**
45+
* Test adding various send addresses to the address book.
46+
*
47+
* There are three cases tested:
48+
*
49+
* - new_address: a new address which should add as a send address successfully.
50+
* - existing_s_address: an existing sending address which won't add successfully.
51+
* - existing_r_address: an existing receiving address which won't add successfully.
52+
*
53+
* In each case, verify the resulting state of the address book and optionally
54+
* the warning message presented to the user.
55+
*/
56+
void TestAddAddressesToSendBook()
57+
{
58+
TestChain100Setup test;
59+
CWallet wallet("mock", WalletDatabase::CreateMock());
60+
bool firstRun;
61+
wallet.LoadWallet(firstRun);
62+
63+
auto build_address = [&wallet]() {
64+
CKey key;
65+
key.MakeNewKey(true);
66+
CTxDestination dest(GetDestinationForKey(
67+
key.GetPubKey(), wallet.m_default_address_type));
68+
69+
return std::make_pair(dest, QString::fromStdString(EncodeDestination(dest)));
70+
};
71+
72+
CTxDestination r_key_dest, s_key_dest;
73+
74+
// Add a preexisting "receive" entry in the address book.
75+
QString preexisting_r_address;
76+
QString r_label("already here (r)");
77+
78+
// Add a preexisting "send" entry in the address book.
79+
QString preexisting_s_address;
80+
QString s_label("already here (s)");
81+
82+
// Define a new address (which should add to the address book successfully).
83+
QString new_address;
84+
85+
std::tie(r_key_dest, preexisting_r_address) = build_address();
86+
std::tie(s_key_dest, preexisting_s_address) = build_address();
87+
std::tie(std::ignore, new_address) = build_address();
88+
89+
{
90+
LOCK(wallet.cs_wallet);
91+
wallet.SetAddressBook(r_key_dest, r_label.toStdString(), "receive");
92+
wallet.SetAddressBook(s_key_dest, s_label.toStdString(), "send");
93+
}
94+
95+
auto check_addbook_size = [&wallet](int expected_size) {
96+
QCOMPARE(static_cast<int>(wallet.mapAddressBook.size()), expected_size);
97+
};
98+
99+
// We should start with the two addresses we added earlier and nothing else.
100+
check_addbook_size(2);
101+
102+
// Initialize relevant QT models.
103+
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
104+
auto node = interfaces::MakeNode();
105+
OptionsModel optionsModel(*node);
106+
AddWallet(&wallet);
107+
WalletModel walletModel(std::move(node->getWallets()[0]), *node, platformStyle.get(), &optionsModel);
108+
RemoveWallet(&wallet);
109+
EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress);
110+
editAddressDialog.setModel(walletModel.getAddressTableModel());
111+
112+
EditAddressAndSubmit(
113+
&editAddressDialog, QString("uhoh"), preexisting_r_address,
114+
QString(
115+
"Address \"%1\" already exists as a receiving address with label "
116+
"\"%2\" and so cannot be added as a sending address."
117+
).arg(preexisting_r_address).arg(r_label));
118+
119+
check_addbook_size(2);
120+
121+
EditAddressAndSubmit(
122+
&editAddressDialog, QString("uhoh, different"), preexisting_s_address,
123+
QString(
124+
"The entered address \"%1\" is already in the address book with "
125+
"label \"%2\"."
126+
).arg(preexisting_s_address).arg(s_label));
127+
128+
check_addbook_size(2);
129+
130+
// Submit a new address which should add successfully - we expect the
131+
// warning message to be blank.
132+
EditAddressAndSubmit(
133+
&editAddressDialog, QString("new"), new_address, QString(""));
134+
135+
check_addbook_size(3);
136+
}
137+
138+
} // namespace
139+
140+
void AddressBookTests::addressBookTests()
141+
{
142+
TestAddAddressesToSendBook();
143+
}

src/qt/test/addressbooktests.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#ifndef BITCOIN_QT_TEST_ADDRESSBOOKTESTS_H
2+
#define BITCOIN_QT_TEST_ADDRESSBOOKTESTS_H
3+
4+
#include <QObject>
5+
#include <QTest>
6+
7+
class AddressBookTests : public QObject
8+
{
9+
Q_OBJECT
10+
11+
private Q_SLOTS:
12+
void addressBookTests();
13+
};
14+
15+
#endif // BITCOIN_QT_TEST_ADDRESSBOOKTESTS_H

0 commit comments

Comments
 (0)