mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 22:21:16 +00:00
chore(wallet): synchronize shared wallet module
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QSettings>
|
||||
#include <QSignalSpy>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTimer>
|
||||
#include <QtTest>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "FakeWalletProvider.h"
|
||||
#include "LogosWalletProvider.h"
|
||||
#include "WalletAccountId.h"
|
||||
@@ -22,6 +29,30 @@ const QString ACCOUNT_C(64, QLatin1Char('d'));
|
||||
const QString PROGRAM_ID(64, QLatin1Char('c'));
|
||||
const QString EOA_OWNER(64, QLatin1Char('0'));
|
||||
|
||||
class ScopedEnvironment final {
|
||||
public:
|
||||
ScopedEnvironment(QByteArray name, QByteArray value)
|
||||
: m_name(std::move(name)),
|
||||
m_hadValue(qEnvironmentVariableIsSet(m_name.constData())),
|
||||
m_previous(qgetenv(m_name.constData()))
|
||||
{
|
||||
qputenv(m_name.constData(), value);
|
||||
}
|
||||
|
||||
~ScopedEnvironment()
|
||||
{
|
||||
if (m_hadValue)
|
||||
qputenv(m_name.constData(), m_previous);
|
||||
else
|
||||
qunsetenv(m_name.constData());
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray m_name;
|
||||
bool m_hadValue;
|
||||
QByteArray m_previous;
|
||||
};
|
||||
|
||||
QString publicAccountJson(const QString& owner = PROGRAM_ID,
|
||||
const QString& balance = QStringLiteral("01000000000000000000000000000000"),
|
||||
const QString& nonce = QString(32, QLatin1Char('0')),
|
||||
@@ -59,16 +90,28 @@ private slots:
|
||||
void fallsBackToBalanceWhenPublicReadFails();
|
||||
void createsAndPersistsAccounts();
|
||||
void preservesCreatedAccountWhenPublicReadFails();
|
||||
void preservesCreatedAccountWhenSnapshotRefreshFails();
|
||||
void createdAccountDoesNotRescanWallet();
|
||||
void dispatchesExactGenericTransaction();
|
||||
void rejectsInvalidSubmissionResponses();
|
||||
void encodesAccountIdsForDisplay();
|
||||
void walletMutationsUseAsyncSdk();
|
||||
void staleAsyncMutationCannotCrossSession();
|
||||
void destroyedProviderIgnoresLateMutation();
|
||||
void exposesStableAccountModelRoles();
|
||||
void encodesAccountIdsForDisplay();
|
||||
void persistsHumanizedWalletPreferences();
|
||||
void fakeProviderImplementsConsumerContract();
|
||||
void controllerOwnsUiWalletFlow();
|
||||
void controllerReportsCreationPersistenceAndRefreshFailures();
|
||||
void controllerSeparatesSnapshotsFromCosmeticState();
|
||||
void controllerOpenDoesNotWaitForWalletSync();
|
||||
void controllerCreationDoesNotWaitForWalletSync();
|
||||
void controllerSeedsDefaultWalletConfigWithConfiguredEndpoint();
|
||||
void controllerPreservesExistingDefaultWalletConfig();
|
||||
void controllerStopsReachabilityChecksAfterDisconnect();
|
||||
void completedAsyncSnapshotReleasesCallback();
|
||||
void deferredCallbacksIgnoreDestroyedController();
|
||||
void newerReachabilityResultWins();
|
||||
void coalescesReachabilityChecksForSameEndpoint();
|
||||
void controllerReportsPartialWalletCreation();
|
||||
};
|
||||
|
||||
void LogosWalletProviderTest::adoptsOpenWalletAndCachesSnapshots()
|
||||
@@ -103,6 +146,7 @@ void LogosWalletProviderTest::adoptsOpenWalletAndCachesSnapshots()
|
||||
|
||||
const int listCalls = modules.logos_execution_zone.listCalls;
|
||||
const int readCalls = modules.logos_execution_zone.publicReadCalls;
|
||||
const int saveCalls = modules.logos_execution_zone.saveCalls;
|
||||
QVERIFY(provider.snapshot().ok());
|
||||
QCOMPARE(modules.logos_execution_zone.listCalls, listCalls);
|
||||
QCOMPARE(modules.logos_execution_zone.publicReadCalls, readCalls);
|
||||
@@ -110,6 +154,7 @@ void LogosWalletProviderTest::adoptsOpenWalletAndCachesSnapshots()
|
||||
QVERIFY(provider.snapshot(true).ok());
|
||||
QVERIFY(modules.logos_execution_zone.listCalls > listCalls);
|
||||
QVERIFY(modules.logos_execution_zone.publicReadCalls > readCalls);
|
||||
QCOMPARE(modules.logos_execution_zone.saveCalls, saveCalls);
|
||||
|
||||
modules.logos_execution_zone.publicAccounts[ACCOUNT_A] = publicAccountJson(
|
||||
PROGRAM_ID, QString(32, QLatin1Char('f')));
|
||||
@@ -236,6 +281,9 @@ void LogosWalletProviderTest::createsAndPersistsWallet()
|
||||
QVERIFY(directory.isValid());
|
||||
|
||||
LogosModules modules;
|
||||
modules.logos_execution_zone.currentBlockHeight = 12;
|
||||
modules.logos_execution_zone.accounts = { accountEntry(ACCOUNT_A, true) };
|
||||
modules.logos_execution_zone.publicAccounts.insert(ACCOUNT_A, publicAccountJson());
|
||||
LogosWalletProvider provider(&modules);
|
||||
const WalletPaths paths {
|
||||
directory.filePath(QStringLiteral("config/wallet.json")),
|
||||
@@ -249,6 +297,9 @@ void LogosWalletProviderTest::createsAndPersistsWallet()
|
||||
QCOMPARE(modules.logos_execution_zone.createdStorage, paths.storage);
|
||||
QCOMPARE(modules.logos_execution_zone.createdPassword, QStringLiteral("secret"));
|
||||
QVERIFY(modules.logos_execution_zone.saveCalls >= 1);
|
||||
QCOMPARE(modules.logos_execution_zone.syncCalls, 0);
|
||||
QCOMPARE(modules.logos_execution_zone.listCalls, 0);
|
||||
QCOMPARE(modules.logos_execution_zone.publicReadCalls, 0);
|
||||
|
||||
LogosModules rejectedModules;
|
||||
rejectedModules.logos_execution_zone.mnemonic.clear();
|
||||
@@ -351,7 +402,7 @@ void LogosWalletProviderTest::preservesCreatedAccountWhenPublicReadFails()
|
||||
QCOMPARE(creation.snapshot.accounts.at(0).balance, QStringLiteral("7"));
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::preservesCreatedAccountWhenSnapshotRefreshFails()
|
||||
void LogosWalletProviderTest::createdAccountDoesNotRescanWallet()
|
||||
{
|
||||
LogosModules modules;
|
||||
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||
@@ -362,11 +413,13 @@ void LogosWalletProviderTest::preservesCreatedAccountWhenSnapshotRefreshFails()
|
||||
|
||||
modules.logos_execution_zone.currentBlockHeight = 1;
|
||||
modules.logos_execution_zone.syncResult = 1;
|
||||
const int syncCalls = modules.logos_execution_zone.syncCalls;
|
||||
const WalletAccountCreation creation = provider.createAccount(true);
|
||||
|
||||
QVERIFY(creation.ok());
|
||||
QCOMPARE(creation.accountId, ACCOUNT_A);
|
||||
QCOMPARE(creation.snapshot.failure, WalletFailure::ReadFailed);
|
||||
QVERIFY(creation.snapshot.ok());
|
||||
QCOMPARE(modules.logos_execution_zone.syncCalls, syncCalls);
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::dispatchesExactGenericTransaction()
|
||||
@@ -427,6 +480,108 @@ void LogosWalletProviderTest::rejectsInvalidSubmissionResponses()
|
||||
WalletFailure::InvalidRequest);
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::walletMutationsUseAsyncSdk()
|
||||
{
|
||||
LogosModules modules;
|
||||
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||
modules.logos_execution_zone.publicAccountId = ACCOUNT_A;
|
||||
modules.logos_execution_zone.publicAccounts.insert(ACCOUNT_A, publicAccountJson());
|
||||
modules.logos_execution_zone.transactionResponse = QStringLiteral(
|
||||
R"({"success":true,"tx_hash":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})");
|
||||
LogosWalletProvider provider(&modules);
|
||||
QVERIFY(provider.connect({}).ok());
|
||||
|
||||
modules.logos_execution_zone.deferPublicAccountCreation = true;
|
||||
bool creationFinished = false;
|
||||
WalletAccountCreation creation;
|
||||
const int listCalls = modules.logos_execution_zone.listCalls;
|
||||
const int syncCalls = modules.logos_execution_zone.syncCalls;
|
||||
provider.createAccountAsync(true, [&](WalletAccountCreation result) {
|
||||
creation = std::move(result);
|
||||
creationFinished = true;
|
||||
});
|
||||
QVERIFY(!creationFinished);
|
||||
QVERIFY(modules.logos_execution_zone.pendingPublicAccountCreation);
|
||||
modules.logos_execution_zone.finishPublicAccountCreation();
|
||||
QVERIFY(creationFinished);
|
||||
QVERIFY(creation.ok());
|
||||
QVERIFY(creation.publicAccount.ok());
|
||||
QCOMPARE(creation.accountId, ACCOUNT_A);
|
||||
QCOMPARE(creation.snapshot.accounts.size(), 1);
|
||||
QCOMPARE(modules.logos_execution_zone.listCalls, listCalls);
|
||||
QCOMPARE(modules.logos_execution_zone.syncCalls, syncCalls);
|
||||
|
||||
WalletTransaction transaction {
|
||||
PROGRAM_ID,
|
||||
{ ACCOUNT_A, ACCOUNT_B },
|
||||
{ true, false },
|
||||
{ 7, 0, 4294967295U },
|
||||
};
|
||||
modules.logos_execution_zone.deferSubmission = true;
|
||||
bool submissionFinished = false;
|
||||
WalletSubmission submission;
|
||||
provider.submitPublicTransactionAsync(
|
||||
transaction, [&](WalletSubmission result) {
|
||||
submission = std::move(result);
|
||||
submissionFinished = true;
|
||||
});
|
||||
QVERIFY(!submissionFinished);
|
||||
QCOMPARE(modules.logos_execution_zone.submittedProgramId, PROGRAM_ID);
|
||||
QCOMPARE(modules.logos_execution_zone.submittedAccountIds, transaction.accountIds);
|
||||
QCOMPARE(modules.logos_execution_zone.submittedSigningRequirements,
|
||||
QVariantList({ true, false }));
|
||||
QCOMPARE(modules.logos_execution_zone.submittedInstruction.toList(),
|
||||
QVariantList({ 7U, 0U, 4294967295U }));
|
||||
modules.logos_execution_zone.finishSubmission();
|
||||
QVERIFY(submissionFinished);
|
||||
QVERIFY(submission.accepted());
|
||||
QCOMPARE(submission.nativeHash, QString(64, QLatin1Char('a')));
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::staleAsyncMutationCannotCrossSession()
|
||||
{
|
||||
LogosModules modules;
|
||||
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||
modules.logos_execution_zone.publicAccountId = ACCOUNT_A;
|
||||
modules.logos_execution_zone.publicAccounts.insert(ACCOUNT_A, publicAccountJson());
|
||||
modules.logos_execution_zone.deferPublicAccountCreation = true;
|
||||
LogosWalletProvider provider(&modules);
|
||||
QVERIFY(provider.connect({}).ok());
|
||||
|
||||
int callbackCount = 0;
|
||||
WalletAccountCreation creation;
|
||||
provider.createAccountAsync(true, [&](WalletAccountCreation result) {
|
||||
++callbackCount;
|
||||
creation = std::move(result);
|
||||
});
|
||||
provider.disconnect();
|
||||
modules.logos_execution_zone.finishPublicAccountCreation();
|
||||
|
||||
QCOMPARE(callbackCount, 1);
|
||||
QCOMPARE(creation.failure, WalletFailure::WalletUnavailable);
|
||||
QCOMPARE(modules.logos_execution_zone.publicReadCalls, 0);
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::destroyedProviderIgnoresLateMutation()
|
||||
{
|
||||
LogosModules modules;
|
||||
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||
modules.logos_execution_zone.transactionResponse = QStringLiteral(
|
||||
R"({"success":true,"tx_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"})");
|
||||
modules.logos_execution_zone.deferSubmission = true;
|
||||
int callbackCount = 0;
|
||||
{
|
||||
LogosWalletProvider provider(&modules);
|
||||
QVERIFY(provider.connect({}).ok());
|
||||
provider.submitPublicTransactionAsync(
|
||||
{ PROGRAM_ID, { ACCOUNT_A }, { true }, { 1 } },
|
||||
[&](WalletSubmission) { ++callbackCount; });
|
||||
}
|
||||
|
||||
modules.logos_execution_zone.finishSubmission();
|
||||
QCOMPARE(callbackCount, 0);
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::exposesStableAccountModelRoles()
|
||||
{
|
||||
WalletAccountModel model;
|
||||
@@ -459,7 +614,7 @@ void LogosWalletProviderTest::exposesStableAccountModelRoles()
|
||||
QVERIFY(!model.data(model.index(2), WalletAccountModel::CanBePrimaryRole).toBool());
|
||||
|
||||
QSignalSpy presentationsChanged(&model, &QAbstractItemModel::dataChanged);
|
||||
model.applyPresentations({
|
||||
const QVector<WalletAccountPresentation> presentations {
|
||||
{
|
||||
ACCOUNT_A,
|
||||
QStringLiteral("program"),
|
||||
@@ -470,7 +625,7 @@ void LogosWalletProviderTest::exposesStableAccountModelRoles()
|
||||
false,
|
||||
},
|
||||
{
|
||||
ACCOUNT_C,
|
||||
walletAccountIdToBase58(ACCOUNT_C),
|
||||
QStringLiteral("token_holding"),
|
||||
QStringLiteral("TEST holding"),
|
||||
QStringLiteral("Token"),
|
||||
@@ -478,7 +633,8 @@ void LogosWalletProviderTest::exposesStableAccountModelRoles()
|
||||
ACCOUNT_A,
|
||||
true,
|
||||
},
|
||||
});
|
||||
};
|
||||
model.applyPresentations(presentations);
|
||||
QCOMPARE(presentationsChanged.count(), 1);
|
||||
QCOMPARE(model.data(model.index(2), WalletAccountModel::SectionRole).toString(),
|
||||
QStringLiteral("hidden"));
|
||||
@@ -490,6 +646,10 @@ void LogosWalletProviderTest::exposesStableAccountModelRoles()
|
||||
model.setAlias(ACCOUNT_C, {});
|
||||
QCOMPARE(model.data(model.index(2), WalletAccountModel::NameRole).toString(),
|
||||
QStringLiteral("TEST holding"));
|
||||
|
||||
QSignalSpy redundantPresentation(&model, &QAbstractItemModel::dataChanged);
|
||||
QVERIFY(!model.applyPresentations(presentations));
|
||||
QCOMPARE(redundantPresentation.count(), 0);
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::encodesAccountIdsForDisplay()
|
||||
@@ -497,9 +657,16 @@ void LogosWalletProviderTest::encodesAccountIdsForDisplay()
|
||||
QCOMPARE(walletAccountIdToBase58(
|
||||
QStringLiteral("00fe99e4fbd4c71f92e47c384c6235244c8cce39b6d6367e1e338eca0ffe01cb")),
|
||||
QStringLiteral("14tAtixMByFyJrcZVyWibitnijLgd59PfyrjdnYzo8La"));
|
||||
QCOMPARE(walletAccountIdFromBase58(
|
||||
QStringLiteral("14tAtixMByFyJrcZVyWibitnijLgd59PfyrjdnYzo8La")),
|
||||
QStringLiteral("00fe99e4fbd4c71f92e47c384c6235244c8cce39b6d6367e1e338eca0ffe01cb"));
|
||||
QCOMPARE(walletAccountIdToBase58(QString(64, QLatin1Char('0'))),
|
||||
QString(32, QLatin1Char('1')));
|
||||
QCOMPARE(walletAccountIdFromBase58(QString(32, QLatin1Char('1'))),
|
||||
QString(64, QLatin1Char('0')));
|
||||
QVERIFY(walletAccountIdToBase58(QStringLiteral("not-an-account-id")).isEmpty());
|
||||
QVERIFY(walletAccountIdFromBase58(QString(32, QLatin1Char('0'))).isEmpty());
|
||||
QVERIFY(walletAccountIdFromBase58(QString(45, QLatin1Char('1'))).isEmpty());
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::persistsHumanizedWalletPreferences()
|
||||
@@ -599,62 +766,161 @@ void LogosWalletProviderTest::controllerOwnsUiWalletFlow()
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::controllerReportsCreationPersistenceAndRefreshFailures()
|
||||
void LogosWalletProviderTest::controllerSeparatesSnapshotsFromCosmeticState()
|
||||
{
|
||||
const QString settingsApplication = QStringLiteral("WalletCreationFailureTest");
|
||||
const QString settingsApplication = QStringLiteral("WalletSnapshotSignalTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.connectResult.snapshot.accounts = {
|
||||
{ ACCOUNT_A, QStringLiteral("5"), true },
|
||||
};
|
||||
provider.snapshotResult = provider.connectResult.snapshot;
|
||||
WalletController controller(provider, settingsApplication);
|
||||
const WalletUiState baseline = controller.state();
|
||||
const int baselineAccountCount = controller.accountModel()->count();
|
||||
QSignalSpy stateChanged(&controller, &WalletController::stateChanged);
|
||||
QSignalSpy snapshotChanged(&controller, &WalletController::snapshotChanged);
|
||||
|
||||
provider.createWalletResult.mnemonic = QStringLiteral("alpha beta gamma");
|
||||
provider.createWalletResult.failure = WalletFailure::SaveFailed;
|
||||
provider.createWalletResult.snapshot.failure = WalletFailure::SaveFailed;
|
||||
QCOMPARE(controller.createWallet(QStringLiteral("config"), QStringLiteral("storage"),
|
||||
QStringLiteral("secret")),
|
||||
QString());
|
||||
QCOMPARE(controller.state().isWalletOpen, baseline.isWalletOpen);
|
||||
QCOMPARE(controller.state().walletExists, baseline.walletExists);
|
||||
QCOMPARE(controller.state().syncStatus, baseline.syncStatus);
|
||||
QCOMPARE(controller.state().syncError, baseline.syncError);
|
||||
QCOMPARE(controller.accountModel()->count(), baselineAccountCount);
|
||||
QVERIFY(controller.open());
|
||||
stateChanged.clear();
|
||||
snapshotChanged.clear();
|
||||
|
||||
QVERIFY(controller.setAccountAlias(ACCOUNT_A, QStringLiteral("Spending")));
|
||||
QCOMPARE(stateChanged.count(), 1);
|
||||
QCOMPARE(snapshotChanged.count(), 0);
|
||||
|
||||
provider.createWalletResult.failure = WalletFailure::ReadFailed;
|
||||
provider.createWalletResult.snapshot.failure = WalletFailure::ReadFailed;
|
||||
QCOMPARE(controller.createWallet(QStringLiteral("config"), QStringLiteral("storage"),
|
||||
QStringLiteral("secret")),
|
||||
QStringLiteral("alpha beta gamma"));
|
||||
QVERIFY(controller.state().isWalletOpen);
|
||||
QVERIFY(controller.state().walletExists);
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("error"));
|
||||
QCOMPARE(controller.state().syncError, QStringLiteral("read_failed"));
|
||||
QCOMPARE(controller.accountModel()->count(), baselineAccountCount);
|
||||
QCOMPARE(snapshotChanged.count(), 0);
|
||||
|
||||
provider.createAccountResult.accountId = ACCOUNT_B;
|
||||
provider.createAccountResult.snapshot.failure = WalletFailure::ReadFailed;
|
||||
QCOMPARE(controller.createAccount(true), ACCOUNT_B);
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("error"));
|
||||
QCOMPARE(controller.state().syncError, QStringLiteral("read_failed"));
|
||||
QCOMPARE(controller.accountModel()->count(), baselineAccountCount);
|
||||
QCOMPARE(snapshotChanged.count(), 0);
|
||||
|
||||
provider.createAccountResult.snapshot = {};
|
||||
provider.createAccountResult.snapshot.accounts = {
|
||||
{ ACCOUNT_A, QStringLiteral("5"), true, QStringLiteral("ok"), EOA_OWNER, {} },
|
||||
{ ACCOUNT_B, QStringLiteral("3"), true, QStringLiteral("ok"), EOA_OWNER, {} },
|
||||
};
|
||||
QCOMPARE(controller.createAccount(true), ACCOUNT_B);
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("ready"));
|
||||
QVERIFY(controller.state().syncError.isEmpty());
|
||||
QCOMPARE(controller.accountModel()->count(), 2);
|
||||
controller.refresh();
|
||||
QCOMPARE(stateChanged.count(), 3);
|
||||
QCOMPARE(snapshotChanged.count(), 1);
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::controllerOpenDoesNotWaitForWalletSync()
|
||||
{
|
||||
const QString settingsApplication = QStringLiteral("WalletAsyncOpenTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.deferAsync = true;
|
||||
provider.connectResult.snapshot.accounts = {
|
||||
{ ACCOUNT_A, QStringLiteral("5"), true },
|
||||
};
|
||||
WalletController controller(provider, settingsApplication);
|
||||
|
||||
QVERIFY(controller.open());
|
||||
QCOMPARE(provider.connectCalls, 1);
|
||||
QVERIFY(!controller.state().isWalletOpen);
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("opening"));
|
||||
QCOMPARE(controller.accountModel()->count(), 0);
|
||||
|
||||
provider.finishConnect();
|
||||
QVERIFY(controller.state().isWalletOpen);
|
||||
QVERIFY(controller.state().canSubmit());
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("ready"));
|
||||
QCOMPARE(controller.accountModel()->count(), 1);
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::controllerCreationDoesNotWaitForWalletSync()
|
||||
{
|
||||
const QString settingsApplication = QStringLiteral("WalletAsyncCreationTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.deferAsync = true;
|
||||
provider.createWalletResult.mnemonic = QStringLiteral("one two three");
|
||||
provider.snapshotResult.accounts = {
|
||||
{ ACCOUNT_A, QStringLiteral("5"), true },
|
||||
};
|
||||
WalletController controller(provider, settingsApplication);
|
||||
|
||||
QCOMPARE(controller.createDefaultWallet(QStringLiteral("secret")),
|
||||
provider.createWalletResult.mnemonic);
|
||||
QCOMPARE(provider.createWalletCalls, 1);
|
||||
QCOMPARE(provider.snapshotCalls, 0);
|
||||
QVERIFY(controller.state().isWalletOpen);
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("syncing"));
|
||||
QVERIFY(!controller.state().canSubmit());
|
||||
QCOMPARE(controller.accountModel()->count(), 0);
|
||||
|
||||
QTRY_COMPARE(provider.snapshotCalls, 1);
|
||||
QVERIFY(provider.lastForceRefresh);
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("syncing"));
|
||||
provider.finishSnapshot();
|
||||
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("ready"));
|
||||
QVERIFY(controller.state().canSubmit());
|
||||
QCOMPARE(controller.accountModel()->count(), 1);
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::controllerSeedsDefaultWalletConfigWithConfiguredEndpoint()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
QVERIFY(directory.isValid());
|
||||
const QString walletHome = directory.filePath(QStringLiteral("wallet"));
|
||||
ScopedEnvironment walletHomeEnvironment(
|
||||
QByteArrayLiteral("LEE_WALLET_HOME_DIR"), walletHome.toLocal8Bit());
|
||||
const QString settingsApplication = QStringLiteral("WalletDefaultEndpointTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.createWalletResult.mnemonic = QStringLiteral("one two three");
|
||||
WalletController controller(provider, settingsApplication);
|
||||
controller.setDefaultSequencerAddress(QStringLiteral("https://testnet.lez.logos.co/"));
|
||||
|
||||
QCOMPARE(controller.createDefaultWallet(QStringLiteral("secret")),
|
||||
provider.createWalletResult.mnemonic);
|
||||
const QString configPath = walletHome + QStringLiteral("/wallet_config.json");
|
||||
QCOMPARE(provider.lastPaths.config, configPath);
|
||||
|
||||
QFile config(configPath);
|
||||
QVERIFY(config.open(QIODevice::ReadOnly));
|
||||
const QJsonDocument document = QJsonDocument::fromJson(config.readAll());
|
||||
QVERIFY(document.isObject());
|
||||
const QJsonObject values = document.object();
|
||||
QCOMPARE(values.value(QStringLiteral("sequencer_addr")).toString(),
|
||||
QStringLiteral("https://testnet.lez.logos.co/"));
|
||||
QCOMPARE(values.value(QStringLiteral("seq_poll_timeout")).toString(),
|
||||
QStringLiteral("12s"));
|
||||
QCOMPARE(values.value(QStringLiteral("seq_tx_poll_max_blocks")).toInt(), 5);
|
||||
QCOMPARE(values.value(QStringLiteral("seq_poll_max_retries")).toInt(), 5);
|
||||
QCOMPARE(values.value(QStringLiteral("seq_block_poll_max_amount")).toInt(), 100);
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::controllerPreservesExistingDefaultWalletConfig()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
QVERIFY(directory.isValid());
|
||||
const QString walletHome = directory.filePath(QStringLiteral("wallet"));
|
||||
QVERIFY(QDir().mkpath(walletHome));
|
||||
const QString configPath = walletHome + QStringLiteral("/wallet_config.json");
|
||||
const QByteArray existingConfig = QByteArrayLiteral(
|
||||
"{\"sequencer_addr\":\"http://127.0.0.1:3040/\",\"custom\":true}");
|
||||
QFile config(configPath);
|
||||
QVERIFY(config.open(QIODevice::WriteOnly));
|
||||
QCOMPARE(config.write(existingConfig), qint64(existingConfig.size()));
|
||||
config.close();
|
||||
|
||||
ScopedEnvironment walletHomeEnvironment(
|
||||
QByteArrayLiteral("LEE_WALLET_HOME_DIR"), walletHome.toLocal8Bit());
|
||||
const QString settingsApplication = QStringLiteral("WalletExistingEndpointTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.createWalletResult.mnemonic = QStringLiteral("one two three");
|
||||
WalletController controller(provider, settingsApplication);
|
||||
controller.setDefaultSequencerAddress(QStringLiteral("https://testnet.lez.logos.co/"));
|
||||
|
||||
QCOMPARE(controller.createDefaultWallet(QStringLiteral("secret")),
|
||||
provider.createWalletResult.mnemonic);
|
||||
QVERIFY(config.open(QIODevice::ReadOnly));
|
||||
QCOMPARE(config.readAll(), existingConfig);
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
@@ -673,19 +939,164 @@ void LogosWalletProviderTest::controllerStopsReachabilityChecksAfterDisconnect()
|
||||
|
||||
QVERIFY(controller.open());
|
||||
QTRY_VERIFY_WITH_TIMEOUT(!finished.isEmpty(), 1000);
|
||||
controller.disconnect();
|
||||
finished.clear();
|
||||
|
||||
auto* timer = controller.findChild<QTimer*>();
|
||||
QVERIFY(timer);
|
||||
QVERIFY(timer->isActive());
|
||||
controller.disconnect();
|
||||
QVERIFY(!timer->isActive());
|
||||
finished.clear();
|
||||
|
||||
timer->setInterval(1);
|
||||
controller.start();
|
||||
QTest::qWait(50);
|
||||
QVERIFY(!timer->isActive());
|
||||
QCOMPARE(finished.count(), 0);
|
||||
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::completedAsyncSnapshotReleasesCallback()
|
||||
{
|
||||
LogosModules modules;
|
||||
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||
LogosWalletProvider provider(&modules);
|
||||
QVERIFY(provider.connect({}).ok());
|
||||
|
||||
bool completed = false;
|
||||
std::weak_ptr<int> callbackLifetime;
|
||||
{
|
||||
auto lifetime = std::make_shared<int>(1);
|
||||
callbackLifetime = lifetime;
|
||||
provider.snapshotAsync(true,
|
||||
[lifetime = std::move(lifetime), &completed](WalletSnapshot snapshot) {
|
||||
QVERIFY(snapshot.ok());
|
||||
completed = true;
|
||||
});
|
||||
}
|
||||
|
||||
QVERIFY(completed);
|
||||
QVERIFY(callbackLifetime.expired());
|
||||
QCOMPARE(modules.logos_execution_zone.saveCalls, 0);
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::deferredCallbacksIgnoreDestroyedController()
|
||||
{
|
||||
const QString settingsApplication = QStringLiteral("WalletDestroyedCallbackTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.deferAsync = true;
|
||||
{
|
||||
auto controller = std::make_unique<WalletController>(provider, settingsApplication);
|
||||
QVERIFY(controller->open());
|
||||
}
|
||||
provider.finishConnect();
|
||||
|
||||
provider.deferAsync = false;
|
||||
{
|
||||
auto controller = std::make_unique<WalletController>(provider, settingsApplication);
|
||||
QVERIFY(controller->open());
|
||||
provider.deferAsync = true;
|
||||
controller->refresh();
|
||||
}
|
||||
provider.finishSnapshot();
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::newerReachabilityResultWins()
|
||||
{
|
||||
const QString settingsApplication = QStringLiteral("WalletReachabilityOrderTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
QTcpServer firstServer;
|
||||
QTcpServer secondServer;
|
||||
QVERIFY(firstServer.listen(QHostAddress::LocalHost));
|
||||
QVERIFY(secondServer.listen(QHostAddress::LocalHost));
|
||||
const QString firstEndpoint = QStringLiteral("http://127.0.0.1:%1")
|
||||
.arg(firstServer.serverPort());
|
||||
const QString secondEndpoint = QStringLiteral("http://127.0.0.1:%1")
|
||||
.arg(secondServer.serverPort());
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.connectResult.snapshot.sequencerAddress = firstEndpoint;
|
||||
WalletController controller(provider, settingsApplication);
|
||||
auto* network = controller.findChild<QNetworkAccessManager*>();
|
||||
QVERIFY(network);
|
||||
QSignalSpy finished(network, &QNetworkAccessManager::finished);
|
||||
|
||||
QVERIFY(controller.open());
|
||||
QTRY_VERIFY(firstServer.hasPendingConnections());
|
||||
QTcpSocket* first = firstServer.nextPendingConnection();
|
||||
QVERIFY(first);
|
||||
|
||||
provider.createAccountResult.accountId = ACCOUNT_B;
|
||||
provider.createAccountResult.snapshot.sequencerAddress = secondEndpoint;
|
||||
QCOMPARE(controller.createAccount(true), ACCOUNT_B);
|
||||
QTRY_VERIFY(secondServer.hasPendingConnections());
|
||||
QTcpSocket* second = secondServer.nextPendingConnection();
|
||||
QVERIFY(second);
|
||||
|
||||
second->write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
|
||||
second->disconnectFromHost();
|
||||
QTRY_COMPARE(finished.size(), 1);
|
||||
QVERIFY(controller.state().sequencerReachable);
|
||||
|
||||
first->disconnectFromHost();
|
||||
QTRY_COMPARE(finished.size(), 2);
|
||||
QVERIFY(controller.state().sequencerReachable);
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::coalescesReachabilityChecksForSameEndpoint()
|
||||
{
|
||||
const QString settingsApplication = QStringLiteral("WalletReachabilityCoalesceTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
QTcpServer server;
|
||||
QVERIFY(server.listen(QHostAddress::LocalHost));
|
||||
const QString endpoint = QStringLiteral("http://127.0.0.1:%1").arg(server.serverPort());
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.connectResult.snapshot.sequencerAddress = endpoint;
|
||||
WalletController controller(provider, settingsApplication);
|
||||
QVERIFY(controller.open());
|
||||
QTRY_VERIFY(server.hasPendingConnections());
|
||||
QTcpSocket* request = server.nextPendingConnection();
|
||||
QVERIFY(request);
|
||||
|
||||
provider.createAccountResult.accountId = ACCOUNT_B;
|
||||
provider.createAccountResult.snapshot.sequencerAddress = endpoint;
|
||||
QCOMPARE(controller.createAccount(true), ACCOUNT_B);
|
||||
QTest::qWait(50);
|
||||
QVERIFY(!server.hasPendingConnections());
|
||||
|
||||
request->write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
|
||||
request->disconnectFromHost();
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
void LogosWalletProviderTest::controllerReportsPartialWalletCreation()
|
||||
{
|
||||
const QString settingsApplication = QStringLiteral("WalletPartialCreationTest");
|
||||
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||
settings.clear();
|
||||
|
||||
FakeWalletProvider provider;
|
||||
provider.createWalletResult.mnemonic = QStringLiteral("one two three");
|
||||
provider.createWalletResult.failure = WalletFailure::SaveFailed;
|
||||
WalletController controller(provider, settingsApplication);
|
||||
|
||||
QCOMPARE(controller.createDefaultWallet(QStringLiteral("secret")),
|
||||
provider.createWalletResult.mnemonic);
|
||||
QVERIFY(!controller.state().isWalletOpen);
|
||||
QCOMPARE(controller.state().syncStatus, QStringLiteral("error"));
|
||||
QCOMPARE(controller.state().syncError, QStringLiteral("save_failed"));
|
||||
settings.clear();
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(LogosWalletProviderTest)
|
||||
|
||||
#include "LogosWalletProviderTest.moc"
|
||||
|
||||
Reference in New Issue
Block a user