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(vendor)
|
||||||
add_subdirectory(libs)
|
add_subdirectory(libs)
|
||||||
add_subdirectory(app)
|
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
|
# TODO: temporary not to duplicate resources until we switch to c++ app then it can be refactored
|
||||||
add_subdirectory(resources)
|
add_subdirectory(resources)
|
||||||
add_subdirectory(ui/imports/assets)
|
add_subdirectory(ui/imports/assets)
|
||||||
|
|
|
@ -21,6 +21,8 @@ target_link_libraries(ApplicationCore
|
||||||
PRIVATE
|
PRIVATE
|
||||||
Qt6::Quick
|
Qt6::Quick
|
||||||
Qt6::Qml
|
Qt6::Qml
|
||||||
|
|
||||||
|
Status::Helpers
|
||||||
)
|
)
|
||||||
|
|
||||||
install(
|
install(
|
||||||
|
@ -42,8 +44,6 @@ target_include_directories(ApplicationCore
|
||||||
|
|
||||||
target_sources(ApplicationCore
|
target_sources(ApplicationCore
|
||||||
PRIVATE
|
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.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/UserConfiguration.cpp
|
${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 "UserConfiguration.h"
|
||||||
|
|
||||||
#include "Conversions.h"
|
#include "Helpers/conversions.h"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ UserConfiguration::UserConfiguration(QObject *parent)
|
||||||
|
|
||||||
const QString UserConfiguration::qmlUserDataFolder() const
|
const QString UserConfiguration::qmlUserDataFolder() const
|
||||||
{
|
{
|
||||||
return toString(m_userDataFolder.string());
|
return toQString(m_userDataFolder.string());
|
||||||
}
|
}
|
||||||
|
|
||||||
const fs::path &UserConfiguration::userDataFolder() const
|
const fs::path &UserConfiguration::userDataFolder() const
|
||||||
|
|
|
@ -6,6 +6,8 @@ project(Helpers
|
||||||
VERSION 0.1.0
|
VERSION 0.1.0
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
|
find_package(nlohmann_json 3.10.5 REQUIRED)
|
||||||
|
|
||||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||||
|
|
||||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
||||||
|
@ -48,6 +50,9 @@ target_include_directories(Helpers
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(Helpers
|
target_link_libraries(Helpers
|
||||||
|
PUBLIC
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
|
||||||
PRIVATE
|
PRIVATE
|
||||||
Qt6::Quick
|
Qt6::Quick
|
||||||
Qt6::Qml
|
Qt6::Qml
|
||||||
|
@ -61,6 +66,8 @@ install(
|
||||||
|
|
||||||
target_sources(Helpers
|
target_sources(Helpers
|
||||||
PRIVATE
|
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/helpers.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.cpp
|
${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
|
Qt6::Concurrent
|
||||||
|
|
||||||
Status::ApplicationCore
|
Status::ApplicationCore
|
||||||
|
Status::Helpers
|
||||||
|
|
||||||
Status::StatusGoQt
|
Status::StatusGoQt
|
||||||
Status::StatusGoConfig
|
Status::StatusGoConfig
|
||||||
|
|
|
@ -11,6 +11,7 @@ SetupNewProfilePageBase {
|
||||||
TempTextInput {
|
TempTextInput {
|
||||||
id: confirmPasswordInput
|
id: confirmPasswordInput
|
||||||
|
|
||||||
|
// TODO: remove this developer helper
|
||||||
text: qsTr("1234567890")
|
text: qsTr("1234567890")
|
||||||
|
|
||||||
width: 416
|
width: 416
|
||||||
|
|
|
@ -48,6 +48,10 @@ OnboardingPageBase {
|
||||||
id: passwordInput
|
id: passwordInput
|
||||||
Layout.preferredWidth: 328
|
Layout.preferredWidth: 328
|
||||||
Layout.preferredHeight: 44
|
Layout.preferredHeight: 44
|
||||||
|
|
||||||
|
// TODO: remove dev helper
|
||||||
|
text: "1234567890"
|
||||||
|
// END dev
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|
|
@ -10,13 +10,17 @@
|
||||||
namespace Status::Onboarding
|
namespace Status::Onboarding
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// TODO: refactor it to MultiAccount
|
||||||
struct AccountDto
|
struct AccountDto
|
||||||
{
|
{
|
||||||
QString name;
|
QString name;
|
||||||
long timestamp;
|
long timestamp;
|
||||||
QString identicon;
|
|
||||||
QString keycardPairing;
|
QString keycardPairing;
|
||||||
QString keyUid;
|
QString keyUid;
|
||||||
|
// TODO images
|
||||||
|
// TODO colorHash
|
||||||
|
// TODO colorId
|
||||||
|
QString address;
|
||||||
|
|
||||||
bool isValid() const
|
bool isValid() const
|
||||||
{
|
{
|
||||||
|
@ -37,9 +41,9 @@ struct AccountDto
|
||||||
if(ok)
|
if(ok)
|
||||||
result.timestamp = t;
|
result.timestamp = t;
|
||||||
}
|
}
|
||||||
result.identicon = Json::getMandatoryProp(jsonObj, "identicon")->toString();
|
|
||||||
result.keycardPairing = Json::getMandatoryProp(jsonObj, "keycard-pairing")->toString();
|
result.keycardPairing = Json::getMandatoryProp(jsonObj, "keycard-pairing")->toString();
|
||||||
result.keyUid = Json::getMandatoryProp(jsonObj, "key-uid")->toString();
|
result.keyUid = Json::getMandatoryProp(jsonObj, "key-uid")->toString();
|
||||||
|
result.address = Json::getProp(jsonObj, "address")->toString();
|
||||||
|
|
||||||
/// TODO: investigate unhandled `photo-path` value
|
/// TODO: investigate unhandled `photo-path` value
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#include "AccountsService.h"
|
#include "AccountsService.h"
|
||||||
|
|
||||||
#include <StatusGo/Accounts/Accounts.h>
|
#include <StatusGo/Accounts/Accounts.h>
|
||||||
|
#include <StatusGo/Accounts/AccountsAPI.h>
|
||||||
#include <StatusGo/General.h>
|
#include <StatusGo/General.h>
|
||||||
#include <StatusGo/Utils.h>
|
#include <StatusGo/Utils.h>
|
||||||
#include <StatusGo/Messenger/Service.h>
|
#include <StatusGo/Messenger/Service.h>
|
||||||
|
|
||||||
#include <ApplicationCore/Conversions.h>
|
#include <Helpers/conversions.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
std::optional<QString>
|
std::optional<QString>
|
||||||
getDataFromFile(const fs::path &path)
|
getDataFromFile(const fs::path &path)
|
||||||
{
|
{
|
||||||
QFile jsonFile{Status::toString(path)};
|
QFile jsonFile{Status::toQString(path)};
|
||||||
if(!jsonFile.open(QIODevice::ReadOnly))
|
if(!jsonFile.open(QIODevice::ReadOnly))
|
||||||
{
|
{
|
||||||
qDebug() << "unable to open" << path.filename().c_str() << " for reading";
|
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());
|
auto gAcc = GeneratedAccountDto::toGeneratedAccountDto(genAddressObj.toObject());
|
||||||
gAcc.alias = generateAlias(gAcc.derivedAccounts.whisper.publicKey);
|
gAcc.alias = generateAlias(gAcc.derivedAccounts.whisper.publicKey);
|
||||||
gAcc.identicon = generateIdenticon(gAcc.derivedAccounts.whisper.publicKey);
|
|
||||||
m_generatedAccounts.push_back(std::move(gAcc));
|
m_generatedAccounts.push_back(std::move(gAcc));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -97,6 +97,7 @@ bool AccountsService::setupAccountAndLogin(const QString &accountId, const QStri
|
||||||
if(StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str()).containsError())
|
if(StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str()).containsError())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
AccountsService::storeAccount(accountId, hashedPassword);
|
||||||
AccountsService::storeDerivedAccounts(accountId, hashedPassword, Constants::General::AccountDefaultPaths);
|
AccountsService::storeDerivedAccounts(accountId, hashedPassword, Constants::General::AccountDefaultPaths);
|
||||||
|
|
||||||
m_loggedInAccount = saveAccountAndLogin(hashedPassword, accountData, subAccountData, settings, nodeConfig);
|
m_loggedInAccount = saveAccountAndLogin(hashedPassword, accountData, subAccountData, settings, nodeConfig);
|
||||||
|
@ -121,8 +122,8 @@ bool AccountsService::isFirstTimeAccountLogin() const
|
||||||
|
|
||||||
bool AccountsService::setKeyStoreDir(const QString &key)
|
bool AccountsService::setKeyStoreDir(const QString &key)
|
||||||
{
|
{
|
||||||
auto keyStoreDir = m_statusgoDataDir / m_keyStoreDirName / key.toStdString();
|
m_keyStoreDir = m_statusgoDataDir / m_keyStoreDirName / key.toStdString();
|
||||||
auto response = StatusGo::General::initKeystore(keyStoreDir.c_str());
|
auto response = StatusGo::General::initKeystore(m_keyStoreDir.c_str());
|
||||||
return !response.containsError();
|
return !response.containsError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ QString AccountsService::login(AccountDto account, const QString& password)
|
||||||
|
|
||||||
QString thumbnailImage;
|
QString thumbnailImage;
|
||||||
QString largeImage;
|
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);
|
thumbnailImage, largeImage);
|
||||||
if(response.containsError())
|
if(response.containsError())
|
||||||
{
|
{
|
||||||
|
@ -173,16 +174,9 @@ QString AccountsService::generateAlias(const QString& publicKey)
|
||||||
return response.result;
|
return response.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AccountsService::generateIdenticon(const QString& publicKey)
|
void AccountsService::deleteMultiAccount(const AccountDto &account)
|
||||||
{
|
{
|
||||||
auto response = StatusGo::Accounts::generateIdenticon(publicKey);
|
StatusGo::Accounts::deleteMultiaccount(account.keyUid, m_keyStoreDir);
|
||||||
if(response.containsError())
|
|
||||||
{
|
|
||||||
qWarning() << response.error.message;
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DerivedAccounts AccountsService::storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
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},
|
return QJsonObject{{"name", displayName.isEmpty() ? account.alias : displayName},
|
||||||
{"address", account.address},
|
{"address", account.address},
|
||||||
{"photo-path", account.identicon},
|
|
||||||
{"identicon", account.identicon},
|
|
||||||
{"key-uid", account.keyUid},
|
{"key-uid", account.keyUid},
|
||||||
{"keycard-pairing", QJsonValue()}};
|
{"keycard-pairing", QJsonValue()}};
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject AccountsService::getAccountDataForAccountId(const QString &accountId, const QString &displayName) const
|
QJsonObject AccountsService::getAccountDataForAccountId(const QString &accountId, const QString &displayName) const
|
||||||
{
|
{
|
||||||
|
|
||||||
for(const GeneratedAccountDto &acc : m_generatedAccounts)
|
for(const GeneratedAccountDto &acc : m_generatedAccounts)
|
||||||
{
|
{
|
||||||
if(acc.id == accountId)
|
if(acc.id == accountId)
|
||||||
|
@ -263,15 +254,16 @@ QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedAccountDt
|
||||||
{"color", "#4360df"},
|
{"color", "#4360df"},
|
||||||
{"wallet", true},
|
{"wallet", true},
|
||||||
{"path", Constants::General::PathDefaultWallet},
|
{"path", Constants::General::PathDefaultWallet},
|
||||||
{"name", "Status account"}
|
{"name", "Status account"},
|
||||||
|
{"derived-from", account.address}
|
||||||
},
|
},
|
||||||
QJsonObject{
|
QJsonObject{
|
||||||
{"public-key", account.derivedAccounts.whisper.publicKey},
|
{"public-key", account.derivedAccounts.whisper.publicKey},
|
||||||
{"address", account.derivedAccounts.whisper.address},
|
{"address", account.derivedAccounts.whisper.address},
|
||||||
{"path", Constants::General::PathWhisper},
|
|
||||||
{"name", displayName.isEmpty() ? account.alias : displayName},
|
{"name", displayName.isEmpty() ? account.alias : displayName},
|
||||||
{"identicon", account.identicon},
|
{"path", Constants::General::PathWhisper},
|
||||||
{"chat", true}
|
{"chat", true},
|
||||||
|
{"derived-from", ""}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -331,20 +323,22 @@ QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedAcc
|
||||||
{"eip1581-address", account.derivedAccounts.eip1581.address},
|
{"eip1581-address", account.derivedAccounts.eip1581.address},
|
||||||
{"dapps-address", account.derivedAccounts.defaultWallet.address},
|
{"dapps-address", account.derivedAccounts.defaultWallet.address},
|
||||||
{"wallet-root-address", account.derivedAccounts.walletRoot.address},
|
{"wallet-root-address", account.derivedAccounts.walletRoot.address},
|
||||||
{"preview-privacy", true},
|
{"preview-privacy?", true},
|
||||||
{"signing-phrase", generateSigningPhrase(3)},
|
{"signing-phrase", generateSigningPhrase(3)},
|
||||||
{"log-level", "INFO"},
|
{"log-level", "INFO"},
|
||||||
{"latest-derived-path", 0},
|
{"latest-derived-path", 0},
|
||||||
{"networks/networks", defaultNetworksJson},
|
|
||||||
{"currency", "usd"},
|
{"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},
|
{"waku-enabled", true},
|
||||||
{"wallet/visible-tokens", {
|
|
||||||
{Constants::General::DefaultNetworkName, QJsonArray{"SNT"}}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{"appearance", 0},
|
{"appearance", 0},
|
||||||
{"networks/current-network", Constants::General::DefaultNetworkName},
|
|
||||||
{"installation-id", installationId}
|
{"installation-id", installationId}
|
||||||
};
|
};
|
||||||
} catch (std::bad_optional_access) {
|
} catch (std::bad_optional_access) {
|
||||||
|
@ -410,6 +404,7 @@ QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId)
|
||||||
|
|
||||||
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
||||||
|
|
||||||
|
nodeConfigJson["KeyStoreDir"] = toQString(fs::relative(m_keyStoreDir, m_statusgoDataDir));
|
||||||
return nodeConfigJson;
|
return nodeConfigJson;
|
||||||
} catch (std::bad_optional_access) {
|
} catch (std::bad_optional_access) {
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
|
|
|
@ -50,7 +50,7 @@ public:
|
||||||
|
|
||||||
QString generateAlias(const QString& publicKey) override;
|
QString generateAlias(const QString& publicKey) override;
|
||||||
|
|
||||||
QString generateIdenticon(const QString& publicKey) override;
|
void deleteMultiAccount(const AccountDto &account) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QJsonObject prepareAccountJsonObject(const GeneratedAccountDto& account, const QString& displayName) const;
|
QJsonObject prepareAccountJsonObject(const GeneratedAccountDto& account, const QString& displayName) const;
|
||||||
|
@ -83,6 +83,7 @@ private:
|
||||||
std::vector<GeneratedAccountDto> m_generatedAccounts;
|
std::vector<GeneratedAccountDto> m_generatedAccounts;
|
||||||
|
|
||||||
fs::path m_statusgoDataDir;
|
fs::path m_statusgoDataDir;
|
||||||
|
fs::path m_keyStoreDir;
|
||||||
bool m_isFirstTimeAccountLogin;
|
bool m_isFirstTimeAccountLogin;
|
||||||
// TODO: don't see the need for this state here
|
// TODO: don't see the need for this state here
|
||||||
AccountDto m_loggedInAccount;
|
AccountDto m_loggedInAccount;
|
||||||
|
|
|
@ -45,7 +45,9 @@ public:
|
||||||
|
|
||||||
virtual QString generateAlias(const QString& publicKey) = 0;
|
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;
|
QString mnemonic;
|
||||||
DerivedAccounts derivedAccounts;
|
DerivedAccounts derivedAccounts;
|
||||||
|
|
||||||
// The following two are set additionally.
|
// set additionally.
|
||||||
QString alias;
|
QString alias;
|
||||||
QString identicon;
|
|
||||||
|
|
||||||
bool isValid() const
|
bool isValid() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,16 +11,8 @@ namespace Status::Onboarding
|
||||||
|
|
||||||
namespace StatusGo = Status::StatusGo;
|
namespace StatusGo = Status::StatusGo;
|
||||||
|
|
||||||
NewAccountController::NewAccountController(std::shared_ptr<AccountsServiceInterface> accountsService, QObject *parent)
|
NewAccountController::NewAccountController(AccountsServiceInterfacePtr accountsService, QObject* parent)
|
||||||
// TODO: remove dev dev setup after the final implementation
|
: m_accountsService(accountsService)
|
||||||
: m_name("TestAccount")
|
|
||||||
, m_nameIsValid(true)
|
|
||||||
, m_password("1234567890")
|
|
||||||
, m_passwordIsValid(true)
|
|
||||||
, m_confirmationPassword("1234567890")
|
|
||||||
, m_confirmationPasswordIsValid(true)
|
|
||||||
// END dev setup
|
|
||||||
, m_accountsService(accountsService)
|
|
||||||
{
|
{
|
||||||
connect(this, &NewAccountController::passwordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
connect(this, &NewAccountController::passwordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
||||||
connect(this, &NewAccountController::confirmationPasswordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
connect(this, &NewAccountController::confirmationPasswordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "OnboardingController.h"
|
#include "OnboardingController.h"
|
||||||
|
|
||||||
|
#include "Accounts/AccountsServiceInterface.h"
|
||||||
#include "NewAccountController.h"
|
#include "NewAccountController.h"
|
||||||
#include "UserAccount.h"
|
#include "UserAccount.h"
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ namespace Status::Onboarding {
|
||||||
|
|
||||||
namespace StatusGo = Status::StatusGo;
|
namespace StatusGo = Status::StatusGo;
|
||||||
|
|
||||||
OnboardingController::OnboardingController(std::shared_ptr<AccountsServiceInterface> accountsService)
|
OnboardingController::OnboardingController(AccountsServiceInterfacePtr accountsService)
|
||||||
: QObject(nullptr)
|
: QObject(nullptr)
|
||||||
, m_accountsService(std::move(accountsService))
|
, m_accountsService(std::move(accountsService))
|
||||||
{
|
{
|
||||||
|
@ -74,4 +75,9 @@ NewAccountController *OnboardingController::newAccountController() const
|
||||||
return m_newAccountController.get();
|
return m_newAccountController.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AccountsServiceInterfacePtr OnboardingController::accountsService() const
|
||||||
|
{
|
||||||
|
return m_accountsService;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "UserAccountsModel.h"
|
#include "UserAccountsModel.h"
|
||||||
|
|
||||||
#include "Accounts/AccountsServiceInterface.h"
|
|
||||||
#include "Accounts/AccountDto.h"
|
#include "Accounts/AccountDto.h"
|
||||||
|
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
@ -14,7 +13,7 @@ namespace Status::Onboarding
|
||||||
{
|
{
|
||||||
|
|
||||||
class UserAccount;
|
class UserAccount;
|
||||||
|
class AccountsServiceInterface;
|
||||||
class NewAccountController;
|
class NewAccountController;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -50,6 +49,7 @@ public:
|
||||||
Q_INVOKABLE NewAccountController *initNewAccountController();
|
Q_INVOKABLE NewAccountController *initNewAccountController();
|
||||||
Q_INVOKABLE void terminateNewAccountController();
|
Q_INVOKABLE void terminateNewAccountController();
|
||||||
NewAccountController *newAccountController() const;
|
NewAccountController *newAccountController() const;
|
||||||
|
std::shared_ptr<AccountsServiceInterface> accountsService() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void accountLoggedIn();
|
void accountLoggedIn();
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <Onboarding/OnboardingController.h>
|
#include <Onboarding/OnboardingController.h>
|
||||||
|
|
||||||
#include <StatusGo/Accounts/Accounts.h>
|
#include <StatusGo/Accounts/Accounts.h>
|
||||||
|
#include <StatusGo/Accounts/AccountsAPI.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
|
|
||||||
namespace Testing = Status::Testing;
|
namespace Testing = Status::Testing;
|
||||||
namespace Onboarding = Status::Onboarding;
|
namespace Onboarding = Status::Onboarding;
|
||||||
|
namespace Accounts = Status::StatusGo::Accounts;
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
@ -86,6 +88,8 @@ ScopedTestAccount::ScopedTestAccount(const std::string &tempTestSubfolderName, c
|
||||||
|
|
||||||
ScopedTestAccount::~ScopedTestAccount()
|
ScopedTestAccount::~ScopedTestAccount()
|
||||||
{
|
{
|
||||||
|
const auto rootAccount = m_onboarding->accountsService()->getLoggedInAccount();
|
||||||
|
m_onboarding->accountsService()->deleteMultiAccount(rootAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopedTestAccount::processMessages(size_t maxWaitTimeMillis, std::function<bool()> shouldWaitUntilTimeout) {
|
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");
|
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
|
Onboarding::OnboardingController *ScopedTestAccount::onboardingController() const
|
||||||
{
|
{
|
||||||
return m_onboarding.get();
|
return m_onboarding.get();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Wallet/WalletApi.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
@ -11,6 +13,9 @@ namespace Status::Onboarding {
|
||||||
class OnboardingController;
|
class OnboardingController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Wallet = Status::StatusGo::Wallet;
|
||||||
|
namespace Accounts = Status::StatusGo::Accounts;
|
||||||
|
|
||||||
namespace Status::Testing {
|
namespace Status::Testing {
|
||||||
|
|
||||||
class AutoCleanTempTestDir;
|
class AutoCleanTempTestDir;
|
||||||
|
@ -31,9 +36,14 @@ public:
|
||||||
void processMessages(size_t millis, std::function<bool()> shouldWaitUntilTimeout);
|
void processMessages(size_t millis, std::function<bool()> shouldWaitUntilTimeout);
|
||||||
void logOut();
|
void logOut();
|
||||||
|
|
||||||
|
static Accounts::MultiAccount firstChatAccount();
|
||||||
|
static Accounts::MultiAccount firstWalletAccount();
|
||||||
|
|
||||||
|
QString password() const { return m_accountPassword; };
|
||||||
|
|
||||||
Status::Onboarding::OnboardingController* onboardingController() const;
|
Status::Onboarding::OnboardingController* onboardingController() const;
|
||||||
|
|
||||||
const std::filesystem::path& fusedTestFolder() const;;
|
const std::filesystem::path& fusedTestFolder() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<AutoCleanTempTestDir> m_fusedTestFolder;
|
std::unique_ptr<AutoCleanTempTestDir> m_fusedTestFolder;
|
||||||
|
|
|
@ -31,7 +31,7 @@ public:
|
||||||
MOCK_METHOD(QString, login, (Onboarding::AccountDto, const QString&), (override));
|
MOCK_METHOD(QString, login, (Onboarding::AccountDto, const QString&), (override));
|
||||||
MOCK_METHOD(void, clear, (), (override));
|
MOCK_METHOD(void, clear, (), (override));
|
||||||
MOCK_METHOD(QString, generateAlias, (const QString&), (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 <IOTestHelpers.h>
|
||||||
#include <Constants.h>
|
#include <Constants.h>
|
||||||
|
|
||||||
|
#include <StatusGo/Accounts/Accounts.h>
|
||||||
|
|
||||||
#include <Onboarding/Accounts/AccountsService.h>
|
#include <Onboarding/Accounts/AccountsService.h>
|
||||||
|
#include <Onboarding/Common/Constants.h>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
@ -14,14 +17,14 @@ namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace Status::Testing {
|
namespace Status::Testing {
|
||||||
|
|
||||||
class AccountsServicesTest : public ::testing::Test
|
class AccountsService : public ::testing::Test
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<Onboarding::AccountsService> m_accountsService;
|
std::unique_ptr<Onboarding::AccountsService> m_accountsService;
|
||||||
std::unique_ptr<Testing::AutoCleanTempTestDir> m_fusedTestFolder;
|
std::unique_ptr<Testing::AutoCleanTempTestDir> m_fusedTestFolder;
|
||||||
|
|
||||||
void SetUp() override {
|
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 = std::make_unique<Onboarding::AccountsService>();
|
||||||
m_accountsService->init(m_fusedTestFolder->tempFolder() / Constants::statusGoDataDirName);
|
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();
|
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";
|
QString testPubKey = "0x04487f44bac3e90825bfa9720148308cb64835bebb7e888f519cebc127223187067629f8b70d0661a35d4af6516b225286";
|
||||||
|
|
||||||
|
|
|
@ -154,7 +154,6 @@ TEST(OnboardingModule, TestLoginEndToEnd)
|
||||||
accountLoggedInError = true;
|
accountLoggedInError = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
//auto errorString = accountsService->login(accounts[0], accountPassword);
|
|
||||||
// Workaround until we reset the status-go state
|
// 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 ourAccountRes = std::find_if(accounts.begin(), accounts.end(), [accountName](const auto &a) { return a.name == accountName; });
|
||||||
auto errorString = accountsService->login(*ourAccountRes, accountPassword);
|
auto errorString = accountsService->login(*ourAccountRes, accountPassword);
|
||||||
|
|
|
@ -8,7 +8,9 @@ project(StatusGoQt
|
||||||
|
|
||||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
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()
|
qt6_standard_project_setup()
|
||||||
|
|
||||||
add_library(${PROJECT_NAME} SHARED)
|
add_library(${PROJECT_NAME} SHARED)
|
||||||
|
@ -16,18 +18,35 @@ add_library(${PROJECT_NAME} SHARED)
|
||||||
# Use by linker only
|
# Use by linker only
|
||||||
set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
|
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}
|
target_link_libraries(${PROJECT_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
Status::Helpers
|
||||||
|
|
||||||
|
PRIVATE
|
||||||
|
Qt6::Gui
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
Qt6::Concurrent
|
Qt6::Concurrent
|
||||||
|
|
||||||
PRIVATE
|
nlohmann_json::nlohmann_json
|
||||||
|
|
||||||
statusgo_shared
|
statusgo_shared
|
||||||
)
|
)
|
||||||
add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
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
|
# 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?)
|
# 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
|
# and current directory (on mac). Use bundle or set rpath relative to executable
|
||||||
|
@ -46,3 +65,32 @@ install(
|
||||||
IMPORTED_RUNTIME_ARTIFACTS
|
IMPORTED_RUNTIME_ARTIFACTS
|
||||||
statusgo_shared
|
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 "Accounts.h"
|
||||||
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "libstatus.h"
|
|
||||||
|
#include <libstatus.h>
|
||||||
|
|
||||||
const int NUMBER_OF_ADDRESSES_TO_GENERATE = 5;
|
const int NUMBER_OF_ADDRESSES_TO_GENERATE = 5;
|
||||||
const int MNEMONIC_PHRASE_LENGTH = 12;
|
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)
|
RpcResponse<QString> generateAlias(const QString& publicKey)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -212,7 +194,7 @@ RpcResponse<QJsonArray> openAccounts(const char* dataDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
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{
|
QJsonObject payload{
|
||||||
{"name", name},
|
{"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,
|
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||||
const QString& identicon, const QString& thumbnail, const QString& large,
|
const QString& thumbnail, const QString& large, const QJsonObject& nodeConfig)
|
||||||
const QJsonObject& nodeConfig)
|
|
||||||
{
|
{
|
||||||
QJsonObject payload{
|
QJsonObject payload{
|
||||||
{"name", name},
|
{"name", name},
|
||||||
|
|
|
@ -8,8 +8,6 @@ namespace Status::StatusGo::Accounts
|
||||||
{
|
{
|
||||||
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths);
|
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths);
|
||||||
|
|
||||||
RpcResponse<QString> generateIdenticon(const QString& publicKey);
|
|
||||||
|
|
||||||
RpcResponse<QString> generateAlias(const QString& publicKey);
|
RpcResponse<QString> generateAlias(const QString& publicKey);
|
||||||
|
|
||||||
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
||||||
|
@ -24,10 +22,10 @@ namespace Status::StatusGo::Accounts
|
||||||
/// opens database and returns accounts list.
|
/// opens database and returns accounts list.
|
||||||
RpcResponse<QJsonArray> openAccounts(const char* dataDirPath);
|
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,
|
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,
|
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||||
const QString& identicon, const QString& thumbnail, const QString& large,
|
const QString& thumbnail, const QString& large, const QJsonObject& nodeConfig);
|
||||||
const QJsonObject& nodeConfig);
|
|
||||||
RpcResponse<QJsonObject> logout();
|
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 "General.h"
|
||||||
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "libstatus.h"
|
|
||||||
|
#include <libstatus.h>
|
||||||
|
|
||||||
namespace Status::StatusGo::General
|
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 <QtConcurrent>
|
||||||
|
|
||||||
#include "libstatus.h"
|
#include <libstatus.h>
|
||||||
|
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
#include "libstatus.h"
|
#include <libstatus.h>
|
||||||
|
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ QJsonArray toJsonArray(const QVector<QString>& value)
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* statusgoCallPrivateRPC(const char* inputJSON) {
|
const char* statusGoCallPrivateRPC(const char* inputJSON) {
|
||||||
// Evil done here! status-go API doesn't follow the proper so we adapt
|
// Evil done here! status-go API doesn't follow the proper const conventions
|
||||||
return CallPrivateRPC(const_cast<char*>(inputJSON));
|
return CallPrivateRPC(const_cast<char*>(inputJSON));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,14 +80,14 @@ RpcResponse<T> buildPrivateRPCResponse(const T& json)
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* statusgoCallPrivateRPC(const char* inputJSON);
|
const char* statusGoCallPrivateRPC(const char* inputJSON);
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
RpcResponse<T> callPrivateRpc(const QByteArray& payload)
|
RpcResponse<T> callPrivateRpc(const QByteArray& payload)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto result = statusgoCallPrivateRPC(payload.data());
|
auto result = statusGoCallPrivateRPC(payload.data());
|
||||||
T jsonResult;
|
T jsonResult;
|
||||||
if(!Utils::checkReceivedResponse(result, 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)
|
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)
|
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||||
|
|
||||||
|
@ -11,12 +11,14 @@ find_package(GTest REQUIRED)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME}
|
add_executable(TestStatusGoQtModule
|
||||||
test_accounts.cpp
|
test_accounts.cpp
|
||||||
test_messaging.cpp
|
test_messaging.cpp
|
||||||
|
test_onboarding.cpp
|
||||||
|
test_wallet.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME}
|
target_link_libraries(TestStatusGoQtModule
|
||||||
PRIVATE
|
PRIVATE
|
||||||
Qt6::Core
|
Qt6::Core
|
||||||
|
|
||||||
|
@ -34,6 +36,6 @@ target_link_libraries(${PROJECT_NAME}
|
||||||
|
|
||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
gtest_add_tests(
|
gtest_add_tests(
|
||||||
TARGET ${PROJECT_NAME}
|
TARGET TestStatusGoQtModule
|
||||||
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
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 <StatusGo/Accounts/Accounts.h>
|
||||||
|
|
||||||
|
#include <Onboarding/Common/Constants.h>
|
||||||
|
#include <Onboarding/OnboardingController.h>
|
||||||
|
#include <StatusGo/Utils.h>
|
||||||
|
|
||||||
#include <IOTestHelpers.h>
|
#include <IOTestHelpers.h>
|
||||||
|
#include <ScopedTestAccount.h>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace Accounts = Status::StatusGo::Accounts;
|
namespace Accounts = Status::StatusGo::Accounts;
|
||||||
namespace StatusGo = Status::StatusGo;
|
namespace StatusGo = Status::StatusGo;
|
||||||
|
namespace Utils = Status::StatusGo::Utils;
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace Status::Testing {
|
namespace Status::Testing {
|
||||||
|
|
||||||
TEST(Onboarding, TestOpenAccountsNoDataFails) {
|
/// \todo fin a way to test the integration within a test environment. Also how about reusing an existing account
|
||||||
AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
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());
|
const auto accounts = Accounts::getAccounts();
|
||||||
EXPECT_FALSE(response.containsError());
|
// TODO: enable after calling reset to status-go
|
||||||
EXPECT_EQ(response.result.count(), 0);
|
//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) {
|
TEST(Accounts, TestGenerateAccountWithDerivedPath)
|
||||||
AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
{
|
||||||
|
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());
|
auto hashedPassword{Utils::hashString(testAccountPassword)};
|
||||||
EXPECT_FALSE(response.containsError());
|
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;
|
const auto chatAccount = testAccount.firstChatAccount();
|
||||||
for (const auto & file : fs::directory_iterator(fusedTestFolder.tempFolder()))
|
Accounts::generateAccountWithDerivedPath(hashedPassword, newTestAccountName,
|
||||||
fileCount++;
|
newTestAccountColor, newTestAccountEmoji,
|
||||||
EXPECT_EQ(fileCount, 0);
|
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 <ScopedTestAccount.h>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <gmock/gmock.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
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
|
/// 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
|
/// \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
|
/// \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;
|
bool nodeReady = false;
|
||||||
QObject::connect(StatusGo::SignalsManager::instance(), &StatusGo::SignalsManager::nodeReady, [&nodeReady](const QString& error) {
|
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