chore(CPP): Test basic functionality for wallet status-go wrapper API
Considerations - MultiAccountStoreAccount is required for generating a new account. MultiAccountStoreDerivedAccounts won't be enough even that it works for creating initial account and login code - Validate the understanding that `rootAccount.address` is `walletAccount.derivedFrom` Updates: 6321
This commit is contained in:
parent
3bfbe104a4
commit
1983443608
|
@ -29,7 +29,7 @@ endif()
|
|||
add_subdirectory(vendor)
|
||||
add_subdirectory(libs)
|
||||
add_subdirectory(app)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(test/libs/StatusGoQt)
|
||||
# TODO: temporary not to duplicate resources until we switch to c++ app then it can be refactored
|
||||
add_subdirectory(resources)
|
||||
add_subdirectory(ui/imports/assets)
|
||||
|
|
|
@ -21,6 +21,8 @@ target_link_libraries(ApplicationCore
|
|||
PRIVATE
|
||||
Qt6::Quick
|
||||
Qt6::Qml
|
||||
|
||||
Status::Helpers
|
||||
)
|
||||
|
||||
install(
|
||||
|
@ -42,8 +44,6 @@ target_include_directories(ApplicationCore
|
|||
|
||||
target_sources(ApplicationCore
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/Conversions.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/Conversions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/UserConfiguration.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/UserConfiguration.cpp
|
||||
)
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
#include "Conversions.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status {
|
||||
|
||||
QString toString(const fs::path &path) {
|
||||
return QString::fromStdString(path.string());
|
||||
}
|
||||
|
||||
fs::path toPath(const QString &pathStr) {
|
||||
return fs::path(pathStr.toStdString());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Status {
|
||||
|
||||
QString toString(const std::filesystem::path& path);
|
||||
std::filesystem::path toPath(const QString& pathStr);
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#include "UserConfiguration.h"
|
||||
|
||||
#include "Conversions.h"
|
||||
#include "Helpers/conversions.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
|
@ -21,7 +21,7 @@ UserConfiguration::UserConfiguration(QObject *parent)
|
|||
|
||||
const QString UserConfiguration::qmlUserDataFolder() const
|
||||
{
|
||||
return toString(m_userDataFolder.string());
|
||||
return toQString(m_userDataFolder.string());
|
||||
}
|
||||
|
||||
const fs::path &UserConfiguration::userDataFolder() const
|
||||
|
|
|
@ -6,6 +6,8 @@ project(Helpers
|
|||
VERSION 0.1.0
|
||||
LANGUAGES CXX)
|
||||
|
||||
find_package(nlohmann_json 3.10.5 REQUIRED)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
||||
|
@ -48,6 +50,9 @@ target_include_directories(Helpers
|
|||
)
|
||||
|
||||
target_link_libraries(Helpers
|
||||
PUBLIC
|
||||
nlohmann_json::nlohmann_json
|
||||
|
||||
PRIVATE
|
||||
Qt6::Quick
|
||||
Qt6::Qml
|
||||
|
@ -61,6 +66,8 @@ install(
|
|||
|
||||
target_sources(Helpers
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/conversions.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/conversions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/helpers.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.cpp
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#include "conversions.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status {
|
||||
|
||||
QString toQString(const std::string &str)
|
||||
{
|
||||
return QString::fromStdString(str);
|
||||
}
|
||||
|
||||
QString toQString(const fs::path &path) {
|
||||
return toQString(path.string());
|
||||
}
|
||||
|
||||
fs::path toPath(const QString &pathStr) {
|
||||
return fs::path(pathStr.toStdString());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Status {
|
||||
|
||||
QString toQString(const std::string& str);
|
||||
QString toQString(const std::filesystem::path& path);
|
||||
std::filesystem::path toPath(const QString& pathStr);
|
||||
|
||||
} // namespace Status
|
||||
|
||||
namespace nlohmann {
|
||||
|
||||
template<>
|
||||
struct adl_serializer<QString> {
|
||||
static void to_json(json& j, const QString& str) {
|
||||
j = str.toStdString();
|
||||
}
|
||||
|
||||
static void from_json(const json& j, QString& str) {
|
||||
str = QString::fromStdString(j.get<std::string>());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct adl_serializer<QColor> {
|
||||
static void to_json(json& j, const QColor& color) {
|
||||
j = color.name();
|
||||
}
|
||||
|
||||
static void from_json(const json& j, QColor& color) {
|
||||
color = QColor(Status::toQString(j.get<std::string>()));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nlohmann
|
|
@ -43,6 +43,7 @@ target_link_libraries(Onboarding
|
|||
Qt6::Concurrent
|
||||
|
||||
Status::ApplicationCore
|
||||
Status::Helpers
|
||||
|
||||
Status::StatusGoQt
|
||||
Status::StatusGoConfig
|
||||
|
|
|
@ -11,6 +11,7 @@ SetupNewProfilePageBase {
|
|||
TempTextInput {
|
||||
id: confirmPasswordInput
|
||||
|
||||
// TODO: remove this developer helper
|
||||
text: qsTr("1234567890")
|
||||
|
||||
width: 416
|
||||
|
|
|
@ -48,6 +48,10 @@ OnboardingPageBase {
|
|||
id: passwordInput
|
||||
Layout.preferredWidth: 328
|
||||
Layout.preferredHeight: 44
|
||||
|
||||
// TODO: remove dev helper
|
||||
text: "1234567890"
|
||||
// END dev
|
||||
}
|
||||
|
||||
Button {
|
||||
|
|
|
@ -10,13 +10,17 @@
|
|||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
// TODO: refactor it to MultiAccount
|
||||
struct AccountDto
|
||||
{
|
||||
QString name;
|
||||
long timestamp;
|
||||
QString identicon;
|
||||
QString keycardPairing;
|
||||
QString keyUid;
|
||||
// TODO images
|
||||
// TODO colorHash
|
||||
// TODO colorId
|
||||
QString address;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
|
@ -37,9 +41,9 @@ struct AccountDto
|
|||
if(ok)
|
||||
result.timestamp = t;
|
||||
}
|
||||
result.identicon = Json::getMandatoryProp(jsonObj, "identicon")->toString();
|
||||
result.keycardPairing = Json::getMandatoryProp(jsonObj, "keycard-pairing")->toString();
|
||||
result.keyUid = Json::getMandatoryProp(jsonObj, "key-uid")->toString();
|
||||
result.address = Json::getProp(jsonObj, "address")->toString();
|
||||
|
||||
/// TODO: investigate unhandled `photo-path` value
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#include "AccountsService.h"
|
||||
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
#include <StatusGo/Accounts/AccountsAPI.h>
|
||||
#include <StatusGo/General.h>
|
||||
#include <StatusGo/Utils.h>
|
||||
#include <StatusGo/Messenger/Service.h>
|
||||
|
||||
#include <ApplicationCore/Conversions.h>
|
||||
#include <Helpers/conversions.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
|
@ -13,7 +14,7 @@
|
|||
std::optional<QString>
|
||||
getDataFromFile(const fs::path &path)
|
||||
{
|
||||
QFile jsonFile{Status::toString(path)};
|
||||
QFile jsonFile{Status::toQString(path)};
|
||||
if(!jsonFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qDebug() << "unable to open" << path.filename().c_str() << " for reading";
|
||||
|
@ -50,7 +51,6 @@ bool AccountsService::init(const fs::path& statusgoDataDir)
|
|||
{
|
||||
auto gAcc = GeneratedAccountDto::toGeneratedAccountDto(genAddressObj.toObject());
|
||||
gAcc.alias = generateAlias(gAcc.derivedAccounts.whisper.publicKey);
|
||||
gAcc.identicon = generateIdenticon(gAcc.derivedAccounts.whisper.publicKey);
|
||||
m_generatedAccounts.push_back(std::move(gAcc));
|
||||
}
|
||||
return true;
|
||||
|
@ -97,6 +97,7 @@ bool AccountsService::setupAccountAndLogin(const QString &accountId, const QStri
|
|||
if(StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str()).containsError())
|
||||
return false;
|
||||
|
||||
AccountsService::storeAccount(accountId, hashedPassword);
|
||||
AccountsService::storeDerivedAccounts(accountId, hashedPassword, Constants::General::AccountDefaultPaths);
|
||||
|
||||
m_loggedInAccount = saveAccountAndLogin(hashedPassword, accountData, subAccountData, settings, nodeConfig);
|
||||
|
@ -121,8 +122,8 @@ bool AccountsService::isFirstTimeAccountLogin() const
|
|||
|
||||
bool AccountsService::setKeyStoreDir(const QString &key)
|
||||
{
|
||||
auto keyStoreDir = m_statusgoDataDir / m_keyStoreDirName / key.toStdString();
|
||||
auto response = StatusGo::General::initKeystore(keyStoreDir.c_str());
|
||||
m_keyStoreDir = m_statusgoDataDir / m_keyStoreDirName / key.toStdString();
|
||||
auto response = StatusGo::General::initKeystore(m_keyStoreDir.c_str());
|
||||
return !response.containsError();
|
||||
}
|
||||
|
||||
|
@ -140,7 +141,7 @@ QString AccountsService::login(AccountDto account, const QString& password)
|
|||
|
||||
QString thumbnailImage;
|
||||
QString largeImage;
|
||||
auto response = StatusGo::Accounts::login(account.name, account.keyUid, hashedPassword, account.identicon,
|
||||
auto response = StatusGo::Accounts::login(account.name, account.keyUid, hashedPassword,
|
||||
thumbnailImage, largeImage);
|
||||
if(response.containsError())
|
||||
{
|
||||
|
@ -173,16 +174,9 @@ QString AccountsService::generateAlias(const QString& publicKey)
|
|||
return response.result;
|
||||
}
|
||||
|
||||
QString AccountsService::generateIdenticon(const QString& publicKey)
|
||||
void AccountsService::deleteMultiAccount(const AccountDto &account)
|
||||
{
|
||||
auto response = StatusGo::Accounts::generateIdenticon(publicKey);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return QString();
|
||||
}
|
||||
|
||||
return response.result;
|
||||
StatusGo::Accounts::deleteMultiaccount(account.keyUid, m_keyStoreDir);
|
||||
}
|
||||
|
||||
DerivedAccounts AccountsService::storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
||||
|
@ -225,15 +219,12 @@ QJsonObject AccountsService::prepareAccountJsonObject(const GeneratedAccountDto&
|
|||
{
|
||||
return QJsonObject{{"name", displayName.isEmpty() ? account.alias : displayName},
|
||||
{"address", account.address},
|
||||
{"photo-path", account.identicon},
|
||||
{"identicon", account.identicon},
|
||||
{"key-uid", account.keyUid},
|
||||
{"keycard-pairing", QJsonValue()}};
|
||||
}
|
||||
|
||||
QJsonObject AccountsService::getAccountDataForAccountId(const QString &accountId, const QString &displayName) const
|
||||
{
|
||||
|
||||
for(const GeneratedAccountDto &acc : m_generatedAccounts)
|
||||
{
|
||||
if(acc.id == accountId)
|
||||
|
@ -263,15 +254,16 @@ QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedAccountDt
|
|||
{"color", "#4360df"},
|
||||
{"wallet", true},
|
||||
{"path", Constants::General::PathDefaultWallet},
|
||||
{"name", "Status account"}
|
||||
{"name", "Status account"},
|
||||
{"derived-from", account.address}
|
||||
},
|
||||
QJsonObject{
|
||||
{"public-key", account.derivedAccounts.whisper.publicKey},
|
||||
{"address", account.derivedAccounts.whisper.address},
|
||||
{"path", Constants::General::PathWhisper},
|
||||
{"name", displayName.isEmpty() ? account.alias : displayName},
|
||||
{"identicon", account.identicon},
|
||||
{"chat", true}
|
||||
{"path", Constants::General::PathWhisper},
|
||||
{"chat", true},
|
||||
{"derived-from", ""}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -331,20 +323,22 @@ QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedAcc
|
|||
{"eip1581-address", account.derivedAccounts.eip1581.address},
|
||||
{"dapps-address", account.derivedAccounts.defaultWallet.address},
|
||||
{"wallet-root-address", account.derivedAccounts.walletRoot.address},
|
||||
{"preview-privacy", true},
|
||||
{"preview-privacy?", true},
|
||||
{"signing-phrase", generateSigningPhrase(3)},
|
||||
{"log-level", "INFO"},
|
||||
{"latest-derived-path", 0},
|
||||
{"networks/networks", defaultNetworksJson},
|
||||
{"currency", "usd"},
|
||||
{"identicon", account.identicon},
|
||||
//{"networks/networks", defaultNetworksJson},
|
||||
{"networks/networks", QJsonArray()},
|
||||
//{"networks/current-network", Constants::General::DefaultNetworkName},
|
||||
{"networks/current-network", ""},
|
||||
{"wallet/visible-tokens", QJsonObject()},
|
||||
//{"wallet/visible-tokens", {
|
||||
// {Constants::General::DefaultNetworkName, QJsonArray{"SNT"}}
|
||||
// }
|
||||
//},
|
||||
{"waku-enabled", true},
|
||||
{"wallet/visible-tokens", {
|
||||
{Constants::General::DefaultNetworkName, QJsonArray{"SNT"}}
|
||||
}
|
||||
},
|
||||
{"appearance", 0},
|
||||
{"networks/current-network", Constants::General::DefaultNetworkName},
|
||||
{"installation-id", installationId}
|
||||
};
|
||||
} catch (std::bad_optional_access) {
|
||||
|
@ -410,6 +404,7 @@ QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId)
|
|||
|
||||
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
||||
|
||||
nodeConfigJson["KeyStoreDir"] = toQString(fs::relative(m_keyStoreDir, m_statusgoDataDir));
|
||||
return nodeConfigJson;
|
||||
} catch (std::bad_optional_access) {
|
||||
return QJsonObject();
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
|
||||
QString generateAlias(const QString& publicKey) override;
|
||||
|
||||
QString generateIdenticon(const QString& publicKey) override;
|
||||
void deleteMultiAccount(const AccountDto &account) override;
|
||||
|
||||
private:
|
||||
QJsonObject prepareAccountJsonObject(const GeneratedAccountDto& account, const QString& displayName) const;
|
||||
|
@ -83,6 +83,7 @@ private:
|
|||
std::vector<GeneratedAccountDto> m_generatedAccounts;
|
||||
|
||||
fs::path m_statusgoDataDir;
|
||||
fs::path m_keyStoreDir;
|
||||
bool m_isFirstTimeAccountLogin;
|
||||
// TODO: don't see the need for this state here
|
||||
AccountDto m_loggedInAccount;
|
||||
|
|
|
@ -45,7 +45,9 @@ public:
|
|||
|
||||
virtual QString generateAlias(const QString& publicKey) = 0;
|
||||
|
||||
virtual QString generateIdenticon(const QString& publicKey) = 0;
|
||||
virtual void deleteMultiAccount(const AccountDto &account) = 0;
|
||||
};
|
||||
|
||||
using AccountsServiceInterfacePtr = std::shared_ptr<AccountsServiceInterface>;
|
||||
|
||||
}
|
||||
|
|
|
@ -102,9 +102,8 @@ struct GeneratedAccountDto
|
|||
QString mnemonic;
|
||||
DerivedAccounts derivedAccounts;
|
||||
|
||||
// The following two are set additionally.
|
||||
// set additionally.
|
||||
QString alias;
|
||||
QString identicon;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
|
|
|
@ -11,16 +11,8 @@ namespace Status::Onboarding
|
|||
|
||||
namespace StatusGo = Status::StatusGo;
|
||||
|
||||
NewAccountController::NewAccountController(std::shared_ptr<AccountsServiceInterface> accountsService, QObject *parent)
|
||||
// TODO: remove dev dev setup after the final implementation
|
||||
: m_name("TestAccount")
|
||||
, m_nameIsValid(true)
|
||||
, m_password("1234567890")
|
||||
, m_passwordIsValid(true)
|
||||
, m_confirmationPassword("1234567890")
|
||||
, m_confirmationPasswordIsValid(true)
|
||||
// END dev setup
|
||||
, m_accountsService(accountsService)
|
||||
NewAccountController::NewAccountController(AccountsServiceInterfacePtr accountsService, QObject* parent)
|
||||
: m_accountsService(accountsService)
|
||||
{
|
||||
connect(this, &NewAccountController::passwordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
||||
connect(this, &NewAccountController::confirmationPasswordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "OnboardingController.h"
|
||||
|
||||
#include "Accounts/AccountsServiceInterface.h"
|
||||
#include "NewAccountController.h"
|
||||
#include "UserAccount.h"
|
||||
|
||||
|
@ -9,7 +10,7 @@ namespace Status::Onboarding {
|
|||
|
||||
namespace StatusGo = Status::StatusGo;
|
||||
|
||||
OnboardingController::OnboardingController(std::shared_ptr<AccountsServiceInterface> accountsService)
|
||||
OnboardingController::OnboardingController(AccountsServiceInterfacePtr accountsService)
|
||||
: QObject(nullptr)
|
||||
, m_accountsService(std::move(accountsService))
|
||||
{
|
||||
|
@ -74,4 +75,9 @@ NewAccountController *OnboardingController::newAccountController() const
|
|||
return m_newAccountController.get();
|
||||
}
|
||||
|
||||
AccountsServiceInterfacePtr OnboardingController::accountsService() const
|
||||
{
|
||||
return m_accountsService;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "UserAccountsModel.h"
|
||||
|
||||
#include "Accounts/AccountsServiceInterface.h"
|
||||
#include "Accounts/AccountDto.h"
|
||||
|
||||
#include <QQmlEngine>
|
||||
|
@ -14,7 +13,7 @@ namespace Status::Onboarding
|
|||
{
|
||||
|
||||
class UserAccount;
|
||||
|
||||
class AccountsServiceInterface;
|
||||
class NewAccountController;
|
||||
|
||||
/*!
|
||||
|
@ -50,6 +49,7 @@ public:
|
|||
Q_INVOKABLE NewAccountController *initNewAccountController();
|
||||
Q_INVOKABLE void terminateNewAccountController();
|
||||
NewAccountController *newAccountController() const;
|
||||
std::shared_ptr<AccountsServiceInterface> accountsService() const;
|
||||
|
||||
signals:
|
||||
void accountLoggedIn();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <Onboarding/OnboardingController.h>
|
||||
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
#include <StatusGo/Accounts/AccountsAPI.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
|
@ -15,6 +16,7 @@
|
|||
|
||||
namespace Testing = Status::Testing;
|
||||
namespace Onboarding = Status::Onboarding;
|
||||
namespace Accounts = Status::StatusGo::Accounts;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
@ -86,6 +88,8 @@ ScopedTestAccount::ScopedTestAccount(const std::string &tempTestSubfolderName, c
|
|||
|
||||
ScopedTestAccount::~ScopedTestAccount()
|
||||
{
|
||||
const auto rootAccount = m_onboarding->accountsService()->getLoggedInAccount();
|
||||
m_onboarding->accountsService()->deleteMultiAccount(rootAccount);
|
||||
}
|
||||
|
||||
void ScopedTestAccount::processMessages(size_t maxWaitTimeMillis, std::function<bool()> shouldWaitUntilTimeout) {
|
||||
|
@ -106,6 +110,28 @@ void ScopedTestAccount::logOut()
|
|||
throw std::runtime_error("ScopedTestAccount - failed logging out");
|
||||
}
|
||||
|
||||
Accounts::MultiAccount ScopedTestAccount::firstChatAccount()
|
||||
{
|
||||
auto accounts = Accounts::getAccounts();
|
||||
auto chatIt = std::find_if(accounts.begin(), accounts.end(), [](const auto& a) {
|
||||
return a.isChat;
|
||||
});
|
||||
if(chatIt == accounts.end())
|
||||
throw std::runtime_error("ScopedTestAccount::chatAccount: account not found");
|
||||
return *chatIt;
|
||||
}
|
||||
|
||||
Accounts::MultiAccount ScopedTestAccount::firstWalletAccount()
|
||||
{
|
||||
auto accounts = Accounts::getAccounts();
|
||||
auto walletIt = std::find_if(accounts.begin(), accounts.end(), [](const auto& a) {
|
||||
return a.isWallet;
|
||||
});
|
||||
if(walletIt == accounts.end())
|
||||
throw std::runtime_error("ScopedTestAccount::firstWalletAccount: account not found");
|
||||
return *walletIt;
|
||||
}
|
||||
|
||||
Onboarding::OnboardingController *ScopedTestAccount::onboardingController() const
|
||||
{
|
||||
return m_onboarding.get();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <Wallet/WalletApi.h>
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
|
@ -11,6 +13,9 @@ namespace Status::Onboarding {
|
|||
class OnboardingController;
|
||||
}
|
||||
|
||||
namespace Wallet = Status::StatusGo::Wallet;
|
||||
namespace Accounts = Status::StatusGo::Accounts;
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
class AutoCleanTempTestDir;
|
||||
|
@ -31,9 +36,14 @@ public:
|
|||
void processMessages(size_t millis, std::function<bool()> shouldWaitUntilTimeout);
|
||||
void logOut();
|
||||
|
||||
static Accounts::MultiAccount firstChatAccount();
|
||||
static Accounts::MultiAccount firstWalletAccount();
|
||||
|
||||
QString password() const { return m_accountPassword; };
|
||||
|
||||
Status::Onboarding::OnboardingController* onboardingController() const;
|
||||
|
||||
const std::filesystem::path& fusedTestFolder() const;;
|
||||
const std::filesystem::path& fusedTestFolder() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AutoCleanTempTestDir> m_fusedTestFolder;
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
MOCK_METHOD(QString, login, (Onboarding::AccountDto, const QString&), (override));
|
||||
MOCK_METHOD(void, clear, (), (override));
|
||||
MOCK_METHOD(QString, generateAlias, (const QString&), (override));
|
||||
MOCK_METHOD(QString, generateIdenticon, (const QString&), (override));
|
||||
MOCK_METHOD(void, deleteMultiAccount, (const Onboarding::AccountDto&), (override));
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
#include <IOTestHelpers.h>
|
||||
#include <Constants.h>
|
||||
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
|
||||
#include <Onboarding/Accounts/AccountsService.h>
|
||||
#include <Onboarding/Common/Constants.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
@ -14,14 +17,14 @@ namespace fs = std::filesystem;
|
|||
|
||||
namespace Status::Testing {
|
||||
|
||||
class AccountsServicesTest : public ::testing::Test
|
||||
class AccountsService : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
std::unique_ptr<Onboarding::AccountsService> m_accountsService;
|
||||
std::unique_ptr<Testing::AutoCleanTempTestDir> m_fusedTestFolder;
|
||||
|
||||
void SetUp() override {
|
||||
m_fusedTestFolder = std::make_unique<Testing::AutoCleanTempTestDir>("AccountsServicesTest");
|
||||
m_fusedTestFolder = std::make_unique<Testing::AutoCleanTempTestDir>("TestAccountsService");
|
||||
m_accountsService = std::make_unique<Onboarding::AccountsService>();
|
||||
m_accountsService->init(m_fusedTestFolder->tempFolder() / Constants::statusGoDataDirName);
|
||||
}
|
||||
|
@ -33,7 +36,7 @@ protected:
|
|||
};
|
||||
|
||||
|
||||
TEST_F(AccountsServicesTest, GeneratedAccounts)
|
||||
TEST_F(AccountsService, GeneratedAccounts)
|
||||
{
|
||||
auto genAccounts = m_accountsService->generatedAccounts();
|
||||
|
||||
|
@ -48,7 +51,7 @@ TEST_F(AccountsServicesTest, GeneratedAccounts)
|
|||
}
|
||||
}
|
||||
|
||||
TEST_F(AccountsServicesTest, DISABLED_GenerateAlias) // temporary disabled till we see what's happening on the status-go side since it doesn't return aliases for any pk
|
||||
TEST_F(AccountsService, DISABLED_GenerateAlias) // temporary disabled till we see what's happening on the status-go side since it doesn't return aliases for any pk
|
||||
{
|
||||
QString testPubKey = "0x04487f44bac3e90825bfa9720148308cb64835bebb7e888f519cebc127223187067629f8b70d0661a35d4af6516b225286";
|
||||
|
||||
|
|
|
@ -154,7 +154,6 @@ TEST(OnboardingModule, TestLoginEndToEnd)
|
|||
accountLoggedInError = true;
|
||||
});
|
||||
|
||||
//auto errorString = accountsService->login(accounts[0], accountPassword);
|
||||
// Workaround until we reset the status-go state
|
||||
auto ourAccountRes = std::find_if(accounts.begin(), accounts.end(), [accountName](const auto &a) { return a.name == accountName; });
|
||||
auto errorString = accountsService->login(*ourAccountRes, accountPassword);
|
||||
|
|
|
@ -8,7 +8,9 @@ project(StatusGoQt
|
|||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core Concurrent REQUIRED)
|
||||
find_package(nlohmann_json 3.10.5 REQUIRED)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core Concurrent Gui REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED)
|
||||
|
@ -16,18 +18,35 @@ add_library(${PROJECT_NAME} SHARED)
|
|||
# Use by linker only
|
||||
set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# TODO: consider adding a private header for parsing and keep json dependency away!?
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
Status::Helpers
|
||||
|
||||
PRIVATE
|
||||
Qt6::Gui
|
||||
Qt6::Core
|
||||
Qt6::Concurrent
|
||||
|
||||
PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
|
||||
statusgo_shared
|
||||
)
|
||||
add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo
|
||||
# TODO: Workaround to QML_ELEMENT Qt6
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo
|
||||
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
# Copy status-go lib close to the executable
|
||||
# Temporary workaround; TODO: see a better alternative that doesn't depend on target order (dedicated dependencies dir?)
|
||||
# and current directory (on mac). Use bundle or set rpath relative to executable
|
||||
|
@ -46,3 +65,32 @@ install(
|
|||
IMPORTED_RUNTIME_ARTIFACTS
|
||||
statusgo_shared
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/General.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/General.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Utils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Utils.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/MultiAccount.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/MultiAccount.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Metadata/api_response.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Metadata/api_response.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/DerivedAddress.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.cpp
|
||||
)
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# Internally we use includes directly
|
||||
# External clients have to explicitly use the module name
|
||||
add_subdirectory(StatusGo)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/StatusGo
|
||||
# TODO: Workaround to QML_ELEMENT Qt6
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/StatusGo
|
||||
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
|
@ -1,7 +1,8 @@
|
|||
#include "Accounts.h"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "libstatus.h"
|
||||
|
||||
#include <libstatus.h>
|
||||
|
||||
const int NUMBER_OF_ADDRESSES_TO_GENERATE = 5;
|
||||
const int MNEMONIC_PHRASE_LENGTH = 12;
|
||||
|
@ -43,25 +44,6 @@ RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths)
|
|||
}
|
||||
}
|
||||
|
||||
RpcResponse<QString> generateIdenticon(const QString& publicKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
QString identicon;
|
||||
if(!publicKey.isEmpty())
|
||||
{
|
||||
identicon = Identicon(publicKey.toUtf8().data());
|
||||
}
|
||||
return Utils::buildPrivateRPCResponse(identicon);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
auto response = RpcResponse<QString>(QString());
|
||||
response.error.message = QObject::tr("an error generating identicon occurred");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
RpcResponse<QString> generateAlias(const QString& publicKey)
|
||||
{
|
||||
try
|
||||
|
@ -212,7 +194,7 @@ RpcResponse<QJsonArray> openAccounts(const char* dataDirPath)
|
|||
}
|
||||
|
||||
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large)
|
||||
const QString& thumbnail, const QString& large)
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"name", name},
|
||||
|
@ -253,8 +235,7 @@ RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const
|
|||
}
|
||||
|
||||
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large,
|
||||
const QJsonObject& nodeConfig)
|
||||
const QString& thumbnail, const QString& large, const QJsonObject& nodeConfig)
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"name", name},
|
||||
|
|
|
@ -8,8 +8,6 @@ namespace Status::StatusGo::Accounts
|
|||
{
|
||||
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths);
|
||||
|
||||
RpcResponse<QString> generateIdenticon(const QString& publicKey);
|
||||
|
||||
RpcResponse<QString> generateAlias(const QString& publicKey);
|
||||
|
||||
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
||||
|
@ -24,10 +22,10 @@ namespace Status::StatusGo::Accounts
|
|||
/// opens database and returns accounts list.
|
||||
RpcResponse<QJsonArray> openAccounts(const char* dataDirPath);
|
||||
|
||||
/// TODO harmonise password parameters (hashed or plain)?
|
||||
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large);
|
||||
const QString& thumbnail, const QString& large);
|
||||
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large,
|
||||
const QJsonObject& nodeConfig);
|
||||
const QString& thumbnail, const QString& large, const QJsonObject& nodeConfig);
|
||||
RpcResponse<QJsonObject> logout();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
#include "AccountsAPI.h"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "Metadata/api_response.h"
|
||||
|
||||
#include <libstatus.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Status::StatusGo::Accounts
|
||||
{
|
||||
|
||||
Accounts::MultiAccounts getAccounts() {
|
||||
// or even nicer with a raw string literal
|
||||
json inputJson = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "accounts_getAccounts"},
|
||||
{"params", json::array()}
|
||||
};
|
||||
|
||||
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||
auto resultJson = json::parse(result);
|
||||
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||
|
||||
return resultJson.get<CallPrivateRpcResponse>().result;
|
||||
}
|
||||
|
||||
void generateAccountWithDerivedPath(const QString &hashedPassword, const QString &name, const QColor &color, const QString &emoji,
|
||||
const QString &path, const QString &derivedFrom)
|
||||
{
|
||||
std::vector<json> params = {hashedPassword, name, color, emoji, path, derivedFrom};
|
||||
json inputJson = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "accounts_generateAccountWithDerivedPath"},
|
||||
{"params", params}
|
||||
};
|
||||
|
||||
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||
auto resultJson = json::parse(result);
|
||||
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||
}
|
||||
|
||||
void addAccountWithMnemonicAndPath(const QString &mnemonic, const QString &hashedPassword, const QString &name,
|
||||
const QColor &color, const QString &emoji, const QString &path)
|
||||
{
|
||||
std::vector<json> params = {mnemonic, hashedPassword, name, color, emoji, path};
|
||||
json inputJson = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "accounts_addAccountWithMnemonicAndPath"},
|
||||
{"params", params}
|
||||
};
|
||||
|
||||
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||
auto resultJson = json::parse(result);
|
||||
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||
}
|
||||
|
||||
void addAccountWatch(const QString &address, const QString &name, const QColor &color, const QString &emoji)
|
||||
{
|
||||
std::vector<json> params = {address, name, color, emoji};
|
||||
json inputJson = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "accounts_addAccountWatch"},
|
||||
{"params", params}
|
||||
};
|
||||
|
||||
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||
auto resultJson = json::parse(result);
|
||||
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||
}
|
||||
|
||||
void deleteAccount(const QString &address)
|
||||
{
|
||||
std::vector<json> params = {address};
|
||||
json inputJson = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "accounts_deleteAccount"},
|
||||
{"params", params}
|
||||
};
|
||||
|
||||
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||
auto resultJson = json::parse(result);
|
||||
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||
}
|
||||
|
||||
void deleteMultiaccount(const QString &keyUID, const fs::path &keyStoreDir)
|
||||
{
|
||||
// We know go bridge won't misbehave with the input arguments
|
||||
auto result = DeleteMultiaccount(const_cast<char*>(keyUID.toStdString().c_str()), const_cast<char*>(keyStoreDir.string().c_str()));
|
||||
auto resultJson = json::parse(result);
|
||||
checkApiError(resultJson);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
#include "Accounts/MultiAccount.h"
|
||||
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Accounts = Status::StatusGo::Accounts;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::StatusGo::Accounts
|
||||
{
|
||||
|
||||
/// \brief Retrieve all available accounts Wallet and Chat
|
||||
/// \note status-go returns accounts in \c CallPrivateRpcResponse.result
|
||||
/// \throws \c CallPrivateRpcError
|
||||
Accounts::MultiAccounts getAccounts();
|
||||
|
||||
/// \brief Generate a new account
|
||||
/// \note the underlying status-go api, SaveAccounts@accounts.go, returns `nil` for \c CallPrivateRpcResponse.result
|
||||
/// \see \c getAccounts
|
||||
/// \throws \c CallPrivateRpcError
|
||||
void generateAccountWithDerivedPath(const QString &password, const QString &name,
|
||||
const QColor &color, const QString &emoji,
|
||||
const QString &path, const QString &derivedFrom);
|
||||
|
||||
/// \brief Add a new account from an existing mnemonic
|
||||
/// \note the underlying status-go api, SaveAccounts@accounts.go, returns `nil` for \c CallPrivateRpcResponse.result
|
||||
/// \see \c getAccounts
|
||||
/// \throws \c CallPrivateRpcError
|
||||
void addAccountWithMnemonicAndPath(const QString &mnemonic, const QString &hashedPassword, const QString &name,
|
||||
const QColor &color, const QString &emoji, const QString &path);
|
||||
|
||||
/// \brief Add a watch only account
|
||||
/// \note the underlying status-go api, SaveAccounts@accounts.go, returns `nil` for \c CallPrivateRpcResponse.result
|
||||
/// \see \c getAccounts
|
||||
/// \throws \c CallPrivateRpcError
|
||||
void addAccountWatch(const QString &address, const QString &name, const QColor &color, const QString &emoji);
|
||||
|
||||
/// \brief Delete an existing account
|
||||
/// \note the underlying status-go api, DeleteAccount@accounts.go, returns `os.Remove(keyFile)`
|
||||
/// \see \c getAccounts
|
||||
/// \throws \c CallPrivateRpcError
|
||||
void deleteAccount(const QString &address);
|
||||
|
||||
/// \brief Delete an existing account
|
||||
/// \note the underlying status-go api, DeleteAccount@accounts.go, returns `os.Remove(keyFile)`
|
||||
/// \see \c getAccounts
|
||||
/// \throws \c CallPrivateRpcError
|
||||
void deleteMultiaccount(const QString &keyUID, const fs::path &keyStoreDir);
|
||||
|
||||
} // namespaces
|
|
@ -0,0 +1,46 @@
|
|||
#include "MultiAccount.h"
|
||||
|
||||
namespace Status::StatusGo::Accounts {
|
||||
|
||||
void to_json(json& j, const MultiAccount& d) {
|
||||
j = {{"address", d.address},
|
||||
{"chat", d.isChat},
|
||||
{"clock", d.clock},
|
||||
{"color", d.color},
|
||||
{"emoji", d.emoji},
|
||||
{"hidden", d.isHidden},
|
||||
{"mixedcase-address", d.mixedcaseAddress},
|
||||
{"name", d.name},
|
||||
{"path", d.path},
|
||||
{"public-key", d.publicKey},
|
||||
{"removed", d.isRemoved},
|
||||
{"wallet", d.isWallet},
|
||||
};
|
||||
|
||||
if(d.derivedFrom != std::nullopt)
|
||||
j["derived-from"] = d.derivedFrom.value();
|
||||
}
|
||||
|
||||
void from_json(const json& j, MultiAccount& d) {
|
||||
j.at("address").get_to(d.address);
|
||||
j.at("chat").get_to(d.isChat);
|
||||
j.at("clock").get_to(d.clock);
|
||||
j.at("color").get_to(d.color);
|
||||
j.at("emoji").get_to(d.emoji);
|
||||
j.at("hidden").get_to(d.isHidden);
|
||||
j.at("mixedcase-address").get_to(d.mixedcaseAddress);
|
||||
j.at("name").get_to(d.name);
|
||||
j.at("removed").get_to(d.isRemoved);
|
||||
j.at("wallet").get_to(d.isWallet);
|
||||
|
||||
constexpr auto pathKey = "path";
|
||||
if(j.contains(pathKey))
|
||||
j.at(pathKey).get_to(d.path);
|
||||
constexpr auto publicKeyKey = "public-key";
|
||||
if(j.contains(publicKeyKey))
|
||||
j.at(publicKeyKey).get_to(d.publicKey);
|
||||
if(d.isWallet && !j.at("derived-from").get<std::string>().empty())
|
||||
d.derivedFrom = j.at("derived-from").get<QString>();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <Helpers/conversions.h>
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Status::StatusGo::Accounts {
|
||||
|
||||
// TODO: rename to MixedAccount
|
||||
// TODO: create custom types or just named types for all. Also fix APIs after this
|
||||
|
||||
/*! \brief Unique wallet account entity
|
||||
*/
|
||||
struct MultiAccount
|
||||
{
|
||||
QString address;
|
||||
bool isChat = false;
|
||||
int clock = -1;
|
||||
QColor color;
|
||||
std::optional<QString> derivedFrom;
|
||||
QString emoji;
|
||||
bool isHidden = false;
|
||||
QString mixedcaseAddress;
|
||||
QString name;
|
||||
QString path;
|
||||
QString publicKey;
|
||||
bool isRemoved = false;
|
||||
bool isWallet = false;
|
||||
};
|
||||
|
||||
using MultiAccounts = std::vector<MultiAccount>;
|
||||
|
||||
void to_json(json& j, const MultiAccount& d);
|
||||
void from_json(const json& j, MultiAccount& d);
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/General.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Utils.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/Accounts.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Messenger/Service.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SignalsManager.h
|
||||
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/General.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Utils.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/Accounts.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Messenger/Service.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SignalsManager.cpp
|
||||
)
|
|
@ -1,7 +1,8 @@
|
|||
#include "General.h"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "libstatus.h"
|
||||
|
||||
#include <libstatus.h>
|
||||
|
||||
namespace Status::StatusGo::General
|
||||
{
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
#include "api_response.h"
|
||||
|
||||
namespace Status::StatusGo {
|
||||
|
||||
void checkApiError(const json &response) {
|
||||
if(response.contains("error")) {
|
||||
const auto &error = response["error"];
|
||||
if(error.is_object()) {
|
||||
const auto apiErr = response["error"].get<ApiErrorResponseWithCode>();
|
||||
throw CallGenericPrepareJsonError(apiErr);
|
||||
}
|
||||
assert(error.is_string());
|
||||
const auto apiError = response.get<ApiErrorResponse>();
|
||||
if(!apiError.error.empty())
|
||||
throw CallGenericMakeJsonError(response.get<ApiErrorResponse>());
|
||||
}
|
||||
}
|
||||
|
||||
/// \throws \c CallPrivateRpcError, \c nlohmann::exception
|
||||
void checkPrivateRpcCallResultAndReportError(const json &response) {
|
||||
if(response.contains("error"))
|
||||
throw CallPrivateRpcError(response.get<CallPrivateRpcErrorResponse>());
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -0,0 +1,163 @@
|
|||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <string>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Status::StatusGo {
|
||||
|
||||
/*!
|
||||
* \brief General API response if an internal status-go error occured
|
||||
*
|
||||
* \see makeJSONResponse@status.go
|
||||
* \see APIResponsee@types.go
|
||||
*
|
||||
* \note update NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE when changing structure's content
|
||||
*/
|
||||
struct ApiErrorResponse {
|
||||
std::string error;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ApiErrorResponse, error)
|
||||
|
||||
/*!
|
||||
* \brief General API response if an internal status-go error occured
|
||||
*
|
||||
* \see prepareJSONResponseWithCode@response.go
|
||||
*
|
||||
* \note update NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE when changing structure's content
|
||||
*/
|
||||
struct JsonError {
|
||||
int code{};
|
||||
std::string message;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(JsonError, code, message)
|
||||
|
||||
/*!
|
||||
* \brief General API response if an internal status-go error occured
|
||||
*
|
||||
* \see prepareJSONResponseWithCode@response.go
|
||||
* \see jsonrpcSuccessfulResponse@response.go
|
||||
* \note update NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE when changing structure's content
|
||||
*/
|
||||
struct ApiErrorResponseWithCode {
|
||||
JsonError error;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ApiErrorResponseWithCode, error)
|
||||
|
||||
/*!
|
||||
* \brief General API response if no error occured
|
||||
*
|
||||
* \see jsonrpcSuccessfulResponse@response.go
|
||||
* \see jsonrpcSuccessfulResponse@call_raw.go
|
||||
* \see prepareJSONResponseWithCode@response.go
|
||||
*
|
||||
* \note update NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE when changing structure's content
|
||||
*/
|
||||
struct ApiResponse {
|
||||
json result;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ApiResponse, result)
|
||||
|
||||
/*!
|
||||
* \brief Response of status-go's \c CallPrivateRPC
|
||||
*
|
||||
* There are multiple stages in calling private RPC and they return the following values
|
||||
*
|
||||
* 1. CallPrivateRPC@status.go returns `APIResponse` with the returned error if the underlying implementation failed
|
||||
* otherwise returns result of CallRaw@call_raw.go, see 2.
|
||||
* - \see makeJSONResponse@status-go
|
||||
* 2. CallRaw@call_raw.go returns newErrorResponse@call_raw.go or newSuccessResponse@call_raw.go
|
||||
*
|
||||
* \see \c libstatus.h
|
||||
*/
|
||||
struct CallPrivateRpcErrorResponse
|
||||
{
|
||||
std::string jsonrpc;
|
||||
int id;
|
||||
JsonError error;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CallPrivateRpcErrorResponse, jsonrpc, id, error)
|
||||
|
||||
/*!
|
||||
* \brief Response of status-go's \c CallPrivateRPC
|
||||
*
|
||||
* There are multiple stages in calling private RPC and they return the following values
|
||||
*
|
||||
* 1. CallPrivateRPC@status.go returns `APIResponse` with the returned error if the underlying implementation failed
|
||||
* otherwise returns result of CallRaw@call_raw.go, see 2.
|
||||
* - \see makeJSONResponse@status-go
|
||||
* 2. CallRaw@call_raw.go returns newErrorResponse@call_raw.go or newSuccessResponse@call_raw.go
|
||||
*
|
||||
* \see \c libstatus.h
|
||||
*/
|
||||
struct CallPrivateRpcResponse
|
||||
{
|
||||
std::string jsonrpc;
|
||||
int id;
|
||||
json result;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CallPrivateRpcResponse, jsonrpc, id, result)
|
||||
|
||||
/*!
|
||||
* \brief Check generic API calls for error
|
||||
* \throws \c CallGenericMakeJsonError or CallGenericPrepareJsonError in case of error
|
||||
*/
|
||||
void checkApiError(const json& response);
|
||||
|
||||
constexpr int defaultErrorCode = -32000;
|
||||
|
||||
class CallGenericMakeJsonError: public std::runtime_error {
|
||||
public:
|
||||
CallGenericMakeJsonError(const ApiErrorResponse error)
|
||||
: std::runtime_error("CallGenericMakeJsonError@status-go failed")
|
||||
, m_error(std::move(error))
|
||||
{}
|
||||
|
||||
const ApiErrorResponse &errorResponse() const { return m_error; };
|
||||
private:
|
||||
const ApiErrorResponse m_error;
|
||||
};
|
||||
|
||||
class CallGenericPrepareJsonError: public std::runtime_error {
|
||||
public:
|
||||
CallGenericPrepareJsonError(const ApiErrorResponseWithCode error)
|
||||
: std::runtime_error("CallGenericPrepareJsonError@status-go failed")
|
||||
, m_error(std::move(error))
|
||||
{}
|
||||
|
||||
const ApiErrorResponseWithCode &errorResponse() const { return m_error; };
|
||||
private:
|
||||
const ApiErrorResponseWithCode m_error;
|
||||
};
|
||||
|
||||
class CallPrivateRpcError: public std::runtime_error {
|
||||
public:
|
||||
CallPrivateRpcError(const CallPrivateRpcErrorResponse error)
|
||||
: std::runtime_error("CallPrivateRPC@status-go failed")
|
||||
, m_error(std::move(error))
|
||||
{}
|
||||
|
||||
const CallPrivateRpcErrorResponse &errorResponse() const { return m_error; };
|
||||
private:
|
||||
const CallPrivateRpcErrorResponse m_error;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief check response from \c CallPrivateRPC call
|
||||
* \param response json api response from \c CallPrivateRPC
|
||||
* \return true if no error found
|
||||
* \throws \c CallPrivateRpcError
|
||||
*/
|
||||
void checkPrivateRpcCallResultAndReportError(const json& response);
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "libstatus.h"
|
||||
#include <libstatus.h>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "Utils.h"
|
||||
|
||||
#include "libstatus.h"
|
||||
#include <libstatus.h>
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
|
@ -15,8 +15,8 @@ QJsonArray toJsonArray(const QVector<QString>& value)
|
|||
return array;
|
||||
}
|
||||
|
||||
const char* statusgoCallPrivateRPC(const char* inputJSON) {
|
||||
// Evil done here! status-go API doesn't follow the proper so we adapt
|
||||
const char* statusGoCallPrivateRPC(const char* inputJSON) {
|
||||
// Evil done here! status-go API doesn't follow the proper const conventions
|
||||
return CallPrivateRPC(const_cast<char*>(inputJSON));
|
||||
}
|
||||
|
||||
|
|
|
@ -80,14 +80,14 @@ RpcResponse<T> buildPrivateRPCResponse(const T& json)
|
|||
return response;
|
||||
}
|
||||
|
||||
const char* statusgoCallPrivateRPC(const char* inputJSON);
|
||||
const char* statusGoCallPrivateRPC(const char* inputJSON);
|
||||
|
||||
template<class T>
|
||||
RpcResponse<T> callPrivateRpc(const QByteArray& payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = statusgoCallPrivateRPC(payload.data());
|
||||
auto result = statusGoCallPrivateRPC(payload.data());
|
||||
T jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult))
|
||||
{
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <Helpers/conversions.h>
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Status::StatusGo::Wallet {
|
||||
|
||||
/*!
|
||||
* \brief Define a derived address as returned by the corresponding API
|
||||
* \note equivalent of status-go's DerivedAddress@api.go
|
||||
* \see \c getDerivedAddressesForPath
|
||||
*/
|
||||
struct DerivedAddress
|
||||
{
|
||||
// TODO create and Address type represents the 20 byte address of an Ethereum account. See https://pkg.go.dev/github.com/ethereum/go-ethereum/common?utm_source=gopls#Address
|
||||
QString address;
|
||||
// TODO: create an Path named type
|
||||
QString path;
|
||||
bool hasActivity = false;
|
||||
bool alreadyCreated = false;
|
||||
};
|
||||
|
||||
using DerivedAddresses = std::vector<DerivedAddress>;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DerivedAddress, address, path, hasActivity, alreadyCreated);
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#include "WalletApi.h"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "Metadata/api_response.h"
|
||||
|
||||
#include <libstatus.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Status::StatusGo::Wallet
|
||||
{
|
||||
|
||||
DerivedAddresses getDerivedAddressesForPath(const QString &hashedPassword, const QString &derivedFrom, const QString &path, int pageSize, int pageNumber)
|
||||
{
|
||||
std::vector<json> params = {hashedPassword, derivedFrom, path, pageSize, pageNumber};
|
||||
json inputJson = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "wallet_getDerivedAddressesForPath"},
|
||||
{"params", params}
|
||||
};
|
||||
|
||||
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||
const auto resultJson = json::parse(result);
|
||||
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||
|
||||
return resultJson.get<CallPrivateRpcResponse>().result;
|
||||
}
|
||||
|
||||
} // namespaces
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
#include "Accounts/MultiAccount.h"
|
||||
#include "DerivedAddress.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Accounts = Status::StatusGo::Accounts;
|
||||
|
||||
namespace Status::StatusGo::Wallet
|
||||
{
|
||||
/// \brief Retrieve a list of derived account addresses
|
||||
/// \see \c generateAccountWithDerivedPath
|
||||
/// \throws \c CallPrivateRpcError
|
||||
DerivedAddresses getDerivedAddressesForPath(const QString &password, const QString &derivedFrom, const QString &path, int pageSize, int pageNumber);
|
||||
|
||||
} // namespaces
|
|
@ -0,0 +1,40 @@
|
|||
# Unit tests for StatusGoQt
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(TestStatusGoQt VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(TestStatusGoQt
|
||||
test_StatusGo.cpp
|
||||
)
|
||||
|
||||
target_include_directories(TestStatusGoQt
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(TestStatusGoQt
|
||||
PRIVATE
|
||||
Qt6::Core
|
||||
|
||||
GTest::gtest
|
||||
GTest::gmock
|
||||
GTest::gtest_main
|
||||
|
||||
Status::StatusGoQt
|
||||
)
|
||||
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_add_tests(
|
||||
TARGET TestStatusGoQt
|
||||
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
)
|
|
@ -0,0 +1,31 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <StatusGo/Metadata/api_response.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
namespace StatusGo = Status::StatusGo;
|
||||
namespace Status::Testing {
|
||||
|
||||
TEST(StatusGoQt, TestJsonParsing)
|
||||
{
|
||||
auto callRawRPCJsonStr = R"({"jsonrpc":"2.0","id":42,"error":{"code":-32601,"message":"Method not found"}})";
|
||||
auto callRawRPCJson = json::parse(callRawRPCJsonStr).get<StatusGo::CallPrivateRpcErrorResponse>();
|
||||
ASSERT_EQ(callRawRPCJson.jsonrpc, "2.0");
|
||||
ASSERT_EQ(callRawRPCJson.id, 42);
|
||||
StatusGo::JsonError expectedJsonError = {-32601, "Method not found"};
|
||||
ASSERT_EQ(callRawRPCJson.error.code, expectedJsonError.code);
|
||||
ASSERT_EQ(callRawRPCJson.error.message, expectedJsonError.message);
|
||||
|
||||
auto callRawRPCBadJsonKeyStr = R"({"unknown":"2.0","id":42,"error":{"code":-32601,"message":"Method not found"}})";
|
||||
ASSERT_THROW(json::parse(callRawRPCBadJsonKeyStr).get<StatusGo::CallPrivateRpcErrorResponse>(), nlohmann::detail::out_of_range);
|
||||
auto callRawRPCBadJsonValStr = R"({"jsonrpc":"2.0","id":42,"error":23})";
|
||||
ASSERT_THROW(json::parse(callRawRPCBadJsonValStr).get<StatusGo::CallPrivateRpcErrorResponse>(), nlohmann::detail::type_error);
|
||||
|
||||
auto statusGoWithResultJsonStr = R"({"result":"0x123"})";
|
||||
auto statusGoWithResultJson = json::parse(statusGoWithResultJsonStr).get<StatusGo::ApiResponse>();
|
||||
ASSERT_EQ(statusGoWithResultJson.result, "0x123");
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -1 +0,0 @@
|
|||
add_subdirectory(libs)
|
|
@ -1,3 +0,0 @@
|
|||
# Libs integration tests
|
||||
#
|
||||
add_subdirectory(StatusGoQt)
|
|
@ -1,6 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(TestStatusGoQt VERSION 0.1.0 LANGUAGES CXX)
|
||||
project(TestStatusGoQtModule VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
|
@ -11,12 +11,14 @@ find_package(GTest REQUIRED)
|
|||
|
||||
enable_testing()
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
add_executable(TestStatusGoQtModule
|
||||
test_accounts.cpp
|
||||
test_messaging.cpp
|
||||
test_onboarding.cpp
|
||||
test_wallet.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
target_link_libraries(TestStatusGoQtModule
|
||||
PRIVATE
|
||||
Qt6::Core
|
||||
|
||||
|
@ -34,6 +36,6 @@ target_link_libraries(${PROJECT_NAME}
|
|||
|
||||
include(GoogleTest)
|
||||
gtest_add_tests(
|
||||
TARGET ${PROJECT_NAME}
|
||||
TARGET TestStatusGoQtModule
|
||||
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
)
|
||||
|
|
|
@ -1,34 +1,240 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <StatusGo/Accounts/AccountsAPI.h>
|
||||
#include <StatusGo/Metadata/api_response.h>
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
|
||||
#include <Onboarding/Common/Constants.h>
|
||||
#include <Onboarding/OnboardingController.h>
|
||||
#include <StatusGo/Utils.h>
|
||||
|
||||
#include <IOTestHelpers.h>
|
||||
#include <ScopedTestAccount.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
|
||||
namespace Accounts = Status::StatusGo::Accounts;
|
||||
namespace StatusGo = Status::StatusGo;
|
||||
namespace Utils = Status::StatusGo::Utils;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
TEST(Onboarding, TestOpenAccountsNoDataFails) {
|
||||
AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
||||
/// \todo fin a way to test the integration within a test environment. Also how about reusing an existing account
|
||||
TEST(AccountsAPI, TestGetAccounts)
|
||||
{
|
||||
constexpr auto testAccountName = "test_get_accounts_name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testAccountName, testAccountPassword, true);
|
||||
|
||||
auto response = Accounts::openAccounts(fusedTestFolder.tempFolder().c_str());
|
||||
EXPECT_FALSE(response.containsError());
|
||||
EXPECT_EQ(response.result.count(), 0);
|
||||
const auto accounts = Accounts::getAccounts();
|
||||
// TODO: enable after calling reset to status-go
|
||||
//ASSERT_EQ(accounts.size(), 2);
|
||||
|
||||
const auto chatIt = std::find_if(accounts.begin(), accounts.end(), [](const auto& a) { return a.isChat; });
|
||||
ASSERT_NE(chatIt, accounts.end());
|
||||
const auto &chatAccount = *chatIt;
|
||||
ASSERT_EQ(chatAccount.name, testAccountName);
|
||||
ASSERT_FALSE(chatAccount.path.isEmpty());
|
||||
ASSERT_FALSE(chatAccount.derivedFrom.has_value());
|
||||
|
||||
const auto walletIt = std::find_if(accounts.begin(), accounts.end(), [](const auto& a) { return a.isWallet; });
|
||||
ASSERT_NE(walletIt, accounts.end());
|
||||
const auto &walletAccount = *walletIt;
|
||||
ASSERT_NE(walletAccount.name, testAccountName);
|
||||
ASSERT_FALSE(walletAccount.path.isEmpty());
|
||||
ASSERT_TRUE(walletAccount.derivedFrom.has_value());
|
||||
}
|
||||
|
||||
TEST(Onboarding, TestOpenAccountsNoDataDoesNotCreateFiles) {
|
||||
AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
||||
TEST(Accounts, TestGenerateAccountWithDerivedPath)
|
||||
{
|
||||
constexpr auto testRootAccountName = "test-generate_account_with_derived_path-name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
|
||||
|
||||
auto response = Accounts::openAccounts(fusedTestFolder.tempFolder().c_str());
|
||||
EXPECT_FALSE(response.containsError());
|
||||
auto hashedPassword{Utils::hashString(testAccountPassword)};
|
||||
const auto newTestAccountName = u"test_generated_new_account-name"_qs;
|
||||
const auto newTestAccountColor = QColor("fuchsia");
|
||||
const auto newTestAccountEmoji = u""_qs;
|
||||
const auto newTestAccountPath = Status::Constants::General::PathWalletRoot;
|
||||
|
||||
int fileCount = 0;
|
||||
for (const auto & file : fs::directory_iterator(fusedTestFolder.tempFolder()))
|
||||
fileCount++;
|
||||
EXPECT_EQ(fileCount, 0);
|
||||
const auto chatAccount = testAccount.firstChatAccount();
|
||||
Accounts::generateAccountWithDerivedPath(hashedPassword, newTestAccountName,
|
||||
newTestAccountColor, newTestAccountEmoji,
|
||||
newTestAccountPath, chatAccount.address);
|
||||
const auto updatedAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||
|
||||
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(),
|
||||
[newTestAccountName = std::as_const(newTestAccountName)](const auto& a) {
|
||||
return a.name == newTestAccountName;
|
||||
});
|
||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||
const auto &newAccount = *newAccountIt;
|
||||
ASSERT_FALSE(newAccount.address.isEmpty());
|
||||
ASSERT_FALSE(newAccount.isChat);
|
||||
ASSERT_FALSE(newAccount.isWallet);
|
||||
ASSERT_EQ(newAccount.color, newTestAccountColor);
|
||||
ASSERT_FALSE(newAccount.derivedFrom.has_value());
|
||||
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
|
||||
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
|
||||
ASSERT_EQ(newAccount.path, newTestAccountPath);
|
||||
ASSERT_FALSE(newAccount.publicKey.isEmpty());
|
||||
}
|
||||
|
||||
TEST(AccountsAPI, TestGenerateAccountWithDerivedPath_WrongPassword)
|
||||
{
|
||||
constexpr auto testRootAccountName = "test-generate_account_with_derived_path-name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
|
||||
|
||||
const auto chatAccount = testAccount.firstChatAccount();
|
||||
try {
|
||||
Accounts::generateAccountWithDerivedPath(Utils::hashString("WrongPassword"), u"test_wrong_pass-name"_qs,
|
||||
QColor("fuchsia"), "", Status::Constants::General::PathWalletRoot,
|
||||
chatAccount.address);
|
||||
FAIL();
|
||||
} catch(const StatusGo::CallPrivateRpcError &exception) {
|
||||
const auto &err = exception.errorResponse();
|
||||
ASSERT_EQ(err.error.code, StatusGo::defaultErrorCode);
|
||||
ASSERT_EQ(err.error.message, "could not decrypt key with given password");
|
||||
}
|
||||
|
||||
const auto updatedAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedAccounts.size(), 2);
|
||||
}
|
||||
|
||||
TEST(AccountsAPI, TestAddAccountWithMnemonicAndPath)
|
||||
{
|
||||
constexpr auto testRootAccountName = "test_root_account-name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
|
||||
|
||||
auto hashedPassword{Utils::hashString(testAccountPassword)};
|
||||
const auto newTestAccountName = u"test_import_from_mnemonic-name"_qs;
|
||||
const auto newTestAccountColor = QColor("fuchsia");
|
||||
const auto newTestAccountEmoji = u""_qs;
|
||||
const auto newTestAccountPath = Status::Constants::General::PathWalletRoot;
|
||||
|
||||
Accounts::addAccountWithMnemonicAndPath("festival october control quarter husband dish throw couch depth stadium cigar whisper",
|
||||
hashedPassword, newTestAccountName, newTestAccountColor, newTestAccountEmoji,
|
||||
newTestAccountPath);
|
||||
const auto updatedAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||
|
||||
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(),
|
||||
[newTestAccountName = std::as_const(newTestAccountName)](const auto& a) {
|
||||
return a.name == newTestAccountName;
|
||||
});
|
||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||
const auto &newAccount = *newAccountIt;
|
||||
ASSERT_FALSE(newAccount.address.isEmpty());
|
||||
ASSERT_FALSE(newAccount.isChat);
|
||||
ASSERT_FALSE(newAccount.isWallet);
|
||||
ASSERT_EQ(newAccount.color, newTestAccountColor);
|
||||
ASSERT_FALSE(newAccount.derivedFrom.has_value());
|
||||
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
|
||||
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
|
||||
ASSERT_EQ(newAccount.path, newTestAccountPath);
|
||||
ASSERT_FALSE(newAccount.publicKey.isEmpty());
|
||||
}
|
||||
|
||||
/// Show that the menmonic is not validated. Client has to validate the user provided mnemonic
|
||||
TEST(AccountsAPI, TestAddAccountWithMnemonicAndPath_WrongMnemonicWorks)
|
||||
{
|
||||
constexpr auto testRootAccountName = "test_root_account-name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
|
||||
|
||||
auto hashedPassword{Utils::hashString(testAccountPassword)};
|
||||
const auto newTestAccountName = u"test_import_from_wrong_mnemonic-name"_qs;
|
||||
const auto newTestAccountColor = QColor("fuchsia");
|
||||
const auto newTestAccountEmoji = u""_qs;
|
||||
const auto newTestAccountPath = Status::Constants::General::PathWalletRoot;
|
||||
|
||||
// Added an inexistent word. The mnemonic is not checked.
|
||||
Accounts::addAccountWithMnemonicAndPath("october control quarter husband dish throw couch depth stadium cigar waku",
|
||||
hashedPassword, newTestAccountName, newTestAccountColor, newTestAccountEmoji,
|
||||
newTestAccountPath);
|
||||
|
||||
const auto updatedAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||
|
||||
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(),
|
||||
[newTestAccountName = std::as_const(newTestAccountName)](const auto& a) {
|
||||
return a.name == newTestAccountName;
|
||||
});
|
||||
|
||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||
const auto &newAccount = *newAccountIt;
|
||||
ASSERT_FALSE(newAccount.address.isEmpty());
|
||||
ASSERT_FALSE(newAccount.isChat);
|
||||
ASSERT_FALSE(newAccount.isWallet);
|
||||
ASSERT_EQ(newAccount.color, newTestAccountColor);
|
||||
ASSERT_FALSE(newAccount.derivedFrom.has_value());
|
||||
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
|
||||
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
|
||||
ASSERT_EQ(newAccount.path, newTestAccountPath);
|
||||
ASSERT_FALSE(newAccount.publicKey.isEmpty());
|
||||
}
|
||||
|
||||
TEST(AccountsAPI, TestAddAccountWatch)
|
||||
{
|
||||
constexpr auto testRootAccountName = "test_root_account-name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
|
||||
|
||||
const auto newTestAccountName = u"test_watch_only-name"_qs;
|
||||
const auto newTestAccountColor = QColor("fuchsia");
|
||||
const auto newTestAccountEmoji = u""_qs;
|
||||
|
||||
Accounts::addAccountWatch("0x145b6B821523afFC346774b41ACC7b77A171BbA4", newTestAccountName, newTestAccountColor, newTestAccountEmoji);
|
||||
const auto updatedAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||
|
||||
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(),
|
||||
[newTestAccountName = std::as_const(newTestAccountName)](const auto& a) {
|
||||
return a.name == newTestAccountName;
|
||||
});
|
||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||
const auto &newAccount = *newAccountIt;
|
||||
ASSERT_FALSE(newAccount.address.isEmpty());
|
||||
ASSERT_FALSE(newAccount.isChat);
|
||||
ASSERT_FALSE(newAccount.isWallet);
|
||||
ASSERT_EQ(newAccount.color, newTestAccountColor);
|
||||
ASSERT_FALSE(newAccount.derivedFrom.has_value());
|
||||
ASSERT_EQ(newAccount.emoji, newTestAccountEmoji);
|
||||
ASSERT_EQ(newAccount.mixedcaseAddress.toUpper(), newAccount.address.toUpper());
|
||||
ASSERT_TRUE(newAccount.path.isEmpty());
|
||||
ASSERT_TRUE(newAccount.publicKey.isEmpty());
|
||||
}
|
||||
|
||||
TEST(AccountsAPI, TestDeleteAccount)
|
||||
{
|
||||
constexpr auto testRootAccountName = "test_root_account-name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
|
||||
|
||||
const auto newTestAccountName = u"test_account_to_delete-name"_qs;
|
||||
const auto newTestAccountColor = QColor("fuchsia");
|
||||
const auto newTestAccountEmoji = u""_qs;
|
||||
|
||||
Accounts::addAccountWatch("0x145b6B821523afFC346774b41ACC7b77A171BbA4", newTestAccountName, newTestAccountColor, newTestAccountEmoji);
|
||||
const auto updatedAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||
|
||||
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(),
|
||||
[newTestAccountName = std::as_const(newTestAccountName)](const auto& a) {
|
||||
return a.name == newTestAccountName;
|
||||
});
|
||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||
const auto &newAccount = *newAccountIt;
|
||||
|
||||
Accounts::deleteAccount(newAccount.address);
|
||||
const auto updatedDefaultAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedDefaultAccounts.size(), 2);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
#include <ScopedTestAccount.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
@ -17,7 +15,7 @@ namespace Status::Testing {
|
|||
/// This is an integration test to check that status-go doesn't crash on apple silicon when starting Me
|
||||
/// \warning the test depends on IO and it is not deterministic, fast, focused or reliable. It is here for validation only
|
||||
/// \todo fin a way to test the integration within a test environment. Also how about reusing an existing account
|
||||
TEST(OnboardingModule, TestStartMessaging)
|
||||
TEST(MessagingApi, TestStartMessaging)
|
||||
{
|
||||
bool nodeReady = false;
|
||||
QObject::connect(StatusGo::SignalsManager::instance(), &StatusGo::SignalsManager::nodeReady, [&nodeReady](const QString& error) {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
#include <StatusGo/Accounts/AccountsAPI.h>
|
||||
#include <StatusGo/Metadata/api_response.h>
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
|
||||
#include <Onboarding/Common/Constants.h>
|
||||
#include <Onboarding/OnboardingController.h>
|
||||
|
||||
#include <IOTestHelpers.h>
|
||||
#include <ScopedTestAccount.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace Accounts = Status::StatusGo::Accounts;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
TEST(OnboardingApi, TestOpenAccountsNoDataFails) {
|
||||
AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
||||
|
||||
auto response = Accounts::openAccounts(fusedTestFolder.tempFolder().c_str());
|
||||
EXPECT_FALSE(response.containsError());
|
||||
EXPECT_EQ(response.result.count(), 0);
|
||||
}
|
||||
|
||||
TEST(OnboardingApi, TestOpenAccountsNoDataCreatesFiles) {
|
||||
AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
||||
|
||||
auto response = Accounts::openAccounts(fusedTestFolder.tempFolder().c_str());
|
||||
EXPECT_FALSE(response.containsError());
|
||||
|
||||
int fileCount = 0;
|
||||
for (const auto & file : fs::directory_iterator(fusedTestFolder.tempFolder()))
|
||||
fileCount++;
|
||||
EXPECT_GT(fileCount, 0);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
#include <StatusGo/Accounts/AccountsAPI.h>
|
||||
#include <StatusGo/Wallet/WalletApi.h>
|
||||
#include <StatusGo/Metadata/api_response.h>
|
||||
|
||||
#include <Onboarding/Accounts/AccountsServiceInterface.h>
|
||||
#include <Onboarding/Common/Constants.h>
|
||||
#include <Onboarding/OnboardingController.h>
|
||||
|
||||
#include <ScopedTestAccount.h>
|
||||
#include <StatusGo/Utils.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace Wallet = Status::StatusGo::Wallet;
|
||||
namespace Utils = Status::StatusGo::Utils;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/// \warning for now this namespace contains integration test to check the basic assumptions of status-go while building the C++ wrapper.
|
||||
/// \warning the tests depend on IO and are not deterministic, fast, focused or reliable. They are here for validation only
|
||||
/// \todo after status-go API coverage all the integration tests should go away and only test the thin wrapper code
|
||||
namespace Status::Testing {
|
||||
|
||||
TEST(WalletApi, TestGetDerivedAddressesForPath)
|
||||
{
|
||||
constexpr auto testRootAccountName = "test_root_account-name";
|
||||
constexpr auto testAccountPassword = "password*";
|
||||
ScopedTestAccount testAccount(test_info_->name(), testRootAccountName, testAccountPassword, true);
|
||||
|
||||
const auto walletAccount = testAccount.firstWalletAccount();
|
||||
const auto chatAccount = testAccount.firstChatAccount();
|
||||
const auto rootAccount = testAccount.onboardingController()->accountsService()->getLoggedInAccount();
|
||||
ASSERT_EQ(rootAccount.address, walletAccount.derivedFrom.value());
|
||||
|
||||
const auto hashedPassword{Utils::hashString(testAccountPassword)};
|
||||
const auto testPath = Status::Constants::General::PathWalletRoot;
|
||||
|
||||
// chatAccount.address
|
||||
const auto chatDerivedAddresses = Wallet::getDerivedAddressesForPath(hashedPassword, chatAccount.address, testPath, 3, 1);
|
||||
// Check that no change is done
|
||||
const auto updatedAccounts = Accounts::getAccounts();
|
||||
ASSERT_EQ(updatedAccounts.size(), 2);
|
||||
|
||||
ASSERT_EQ(chatDerivedAddresses.size(), 3);
|
||||
// all alreadyCreated are false
|
||||
ASSERT_TRUE(std::none_of(chatDerivedAddresses.begin(), chatDerivedAddresses.end(), [](const auto& a) { return a.alreadyCreated; }));
|
||||
// all hasActivity are false
|
||||
ASSERT_TRUE(std::none_of(chatDerivedAddresses.begin(), chatDerivedAddresses.end(), [](const auto& a) { return a.hasActivity; }));
|
||||
// all address are valid
|
||||
ASSERT_TRUE(std::none_of(chatDerivedAddresses.begin(), chatDerivedAddresses.end(), [](const auto& a) { return a.address.isEmpty(); }));
|
||||
|
||||
const auto walletDerivedAddresses = Wallet::getDerivedAddressesForPath(hashedPassword, walletAccount.address, testPath, 2, 1);
|
||||
ASSERT_EQ(walletDerivedAddresses.size(), 2);
|
||||
// all alreadyCreated are false
|
||||
ASSERT_TRUE(std::none_of(walletDerivedAddresses.begin(), walletDerivedAddresses.end(), [](const auto& a) { return a.alreadyCreated; }));
|
||||
|
||||
const auto rootDerivedAddresses = Wallet::getDerivedAddressesForPath(hashedPassword, rootAccount.address, testPath, 4, 1);
|
||||
ASSERT_EQ(rootDerivedAddresses.size(), 4);
|
||||
ASSERT_EQ(std::count_if(rootDerivedAddresses.begin(), rootDerivedAddresses.end(), [](const auto& a) { return a.alreadyCreated; }), 1);
|
||||
const auto &existingAddress = *std::find_if(rootDerivedAddresses.begin(), rootDerivedAddresses.end(), [](const auto& a) { return a.alreadyCreated; });
|
||||
ASSERT_EQ(existingAddress.address, walletAccount.address);
|
||||
ASSERT_FALSE(existingAddress.hasActivity);
|
||||
}
|
||||
|
||||
} // namespace
|
Loading…
Reference in New Issue