ZaStoGram_desktop/Telegram/SourceFiles/gram/tests/gram_emulate_tests.cpp
John Preston 57b1f2f22a Rotate the wallet key when disabling the backup
Task: 2026/09/01/rotate-wallet-key-on-backup-disable
2026-10-08 22:23:54 +04:00

348 lines
9.6 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "gram/tests/gram_tests.h"
#include "gram/api/gram_api_emulate.h"
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <vector>
namespace Gram::Tests {
namespace {
const auto kAccount = u"0:1111111111111111111111111111111111111111111111111111111111111111"_q;
const auto kFees = u"11"_q;
[[nodiscard]] QJsonObject Transaction(
const QString &account,
const QString &totalFees,
bool aborted,
bool computeSuccess,
bool actionSuccess,
int exitCode,
int resultCode) {
return QJsonObject{
{ u"account"_q, account },
{ u"total_fees"_q, totalFees },
{ u"description"_q, QJsonObject{
{ u"type"_q, u"ord"_q },
{ u"aborted"_q, aborted },
{ u"compute_ph"_q, QJsonObject{
{ u"success"_q, computeSuccess },
{ u"exit_code"_q, exitCode },
} },
{ u"action"_q, QJsonObject{
{ u"success"_q, actionSuccess },
{ u"result_code"_q, resultCode },
} },
} },
};
}
[[nodiscard]] QJsonObject Successful() {
return Transaction(kAccount, kFees, false, true, true, 0, 0);
}
[[nodiscard]] QJsonObject WithoutDescriptionKey(
QJsonObject root,
const QString &key) {
auto description = root.value(u"description"_q).toObject();
description.remove(key);
root.insert(u"description"_q, description);
return root;
}
[[nodiscard]] QByteArray Body(
const QJsonObject &root,
const QString &rootHash = u"root"_q) {
return QJsonDocument(QJsonObject{
{ u"mc_block_seqno"_q, 17 },
{ u"trace"_q, QJsonObject{ { u"tx_hash"_q, rootHash } } },
{ u"transactions"_q, QJsonObject{ { u"root"_q, root } } },
{ u"is_incomplete"_q, false },
}).toJson(QJsonDocument::Compact);
}
[[nodiscard]] QByteArray Body(
const QString &account,
const QString &totalFees,
bool aborted,
bool computeSuccess,
bool actionSuccess,
int exitCode,
int resultCode) {
return Body(Transaction(
account,
totalFees,
aborted,
computeSuccess,
actionSuccess,
exitCode,
resultCode));
}
[[nodiscard]] QString CheckRejected(
const QByteArray &body,
const QString &what) {
if (ParseEmulatedTrace(body)) {
return u"expected nullopt for "_q
+ what
+ u": "_q
+ QString::fromUtf8(body);
}
return QString();
}
} // namespace
std::vector<Check> EmulateChecks() {
return {
{ u"emulate_request_shape"_q, [] {
const auto boc = u"AAAA"_q;
const auto request = EmulateTraceRequest(boc);
const auto expected = QJsonDocument(QJsonObject{
{ u"boc"_q, boc },
{ u"ignore_chksig"_q, true },
{ u"include_code_data"_q, false },
{ u"include_address_book"_q, false },
{ u"include_metadata"_q, false },
{ u"with_actions"_q, true },
{ u"mc_block_seqno"_q, QJsonValue(QJsonValue::Null) },
}).toJson(QJsonDocument::Compact);
const auto failure = CheckRequest(
request,
true,
u"/api/emulate/v1/emulateTrace"_q,
QString(),
expected);
if (!failure.isEmpty()) {
return failure;
}
const auto document = QJsonDocument::fromJson(request.payload);
if (document.isNull() || !document.isObject()) {
return u"payload: not a JSON object"_q;
}
const auto object = document.object();
if (object.size() != 7) {
return u"payload keys: got "_q
+ QString::number(object.size())
+ u", expected 7"_q;
}
struct Flag {
QString key;
bool value = false;
};
const auto flags = std::vector<Flag>{
{ u"ignore_chksig"_q, true },
{ u"include_code_data"_q, false },
{ u"include_address_book"_q, false },
{ u"include_metadata"_q, false },
{ u"with_actions"_q, true },
};
for (const auto &flag : flags) {
const auto value = object.value(flag.key);
if (!value.isBool() || value.toBool() != flag.value) {
return flag.key
+ u": expected the bool "_q
+ (flag.value ? u"true"_q : u"false"_q);
}
}
const auto bocValue = object.value(u"boc"_q);
if (!bocValue.isString() || bocValue.toString() != boc) {
return u"boc: expected the input verbatim"_q;
} else if (!object.value(u"mc_block_seqno"_q).isNull()) {
return u"mc_block_seqno: expected null"_q;
}
return QString();
} },
{ u"emulate_parse_root_fee"_q, [] {
const auto parsed = ParseEmulatedTrace(
Body(kAccount, kFees, false, true, true, 0, 0));
if (!parsed) {
return u"parse failed for a successful root"_q;
} else if (parsed->feeNano != 11) {
return u"feeNano: got "_q
+ QString::number(parsed->feeNano)
+ u", expected 11"_q;
} else if (parsed->account != kAccount) {
return u"account: got "_q
+ parsed->account
+ u", expected "_q
+ kAccount;
}
const auto zero = ParseEmulatedTrace(
Body(kAccount, u"0"_q, false, true, true, 0, 0));
if (!zero) {
return u"parse failed for a zero fee"_q;
} else if (zero->feeNano != 0) {
return u"zero fee: got "_q
+ QString::number(zero->feeNano)
+ u", expected 0"_q;
}
return QString();
} },
{ u"emulate_parse_exit_code_one"_q, [] {
const auto parsed = ParseEmulatedTrace(
Body(kAccount, kFees, false, true, true, 1, 0));
if (!parsed) {
return u"exit code 1: expected the alternative TVM "
u"success code to be accepted"_q;
} else if (parsed->feeNano != 11) {
return u"exit code 1: got feeNano "_q
+ QString::number(parsed->feeNano)
+ u", expected 11"_q;
}
return QString();
} },
{ u"emulate_parse_rejects_failed_phases"_q, [] {
struct Case {
bool computeSuccess = false;
bool actionSuccess = false;
int exitCode = 0;
int resultCode = 0;
QString what;
};
const auto cases = std::vector<Case>{
{ true, true, 33, 0, u"compute exit code 33"_q },
{ true, true, 0, 34, u"action result code 34"_q },
{ true, true, 2, 0, u"compute exit code 2"_q },
{ true, true, 0, 1, u"action result code 1"_q },
{ false, true, 0, 0, u"compute success false"_q },
{ true, false, 0, 0, u"action success false"_q },
};
for (const auto &entry : cases) {
const auto failure = CheckRejected(
Body(
kAccount,
kFees,
false,
entry.computeSuccess,
entry.actionSuccess,
entry.exitCode,
entry.resultCode),
entry.what);
if (!failure.isEmpty()) {
return failure;
}
}
return QString();
} },
{ u"emulate_parse_rejects_missing_phase"_q, [] {
const auto keys = std::vector<QString>{
u"action"_q,
u"compute_ph"_q,
};
for (const auto &key : keys) {
const auto failure = CheckRejected(
Body(WithoutDescriptionKey(Successful(), key)),
u"a root without "_q + key);
if (!failure.isEmpty()) {
return failure;
}
}
auto root = Successful();
auto description = root.value(u"description"_q).toObject();
description.insert(u"compute_ph"_q, QJsonObject{
{ u"success"_q, true },
});
root.insert(u"description"_q, description);
return CheckRejected(Body(root), u"a compute phase without a code"_q);
} },
{ u"emulate_parse_rejects_aborted"_q, [] {
const auto aborted = CheckRejected(
Body(kAccount, kFees, true, true, true, 0, 0),
u"an aborted root"_q);
if (!aborted.isEmpty()) {
return aborted;
}
return CheckRejected(
Body(WithoutDescriptionKey(Successful(), u"aborted"_q)),
u"a root without the aborted flag"_q);
} },
{ u"emulate_parse_rejects_missing_root"_q, [] {
const auto missing = CheckRejected(
Body(Successful(), u"other"_q),
u"a tx_hash naming no transaction"_q);
if (!missing.isEmpty()) {
return missing;
}
const auto empty = CheckRejected(
Body(Successful(), QString()),
u"an empty tx_hash"_q);
if (!empty.isEmpty()) {
return empty;
}
auto object = QJsonDocument::fromJson(Body(Successful())).object();
object.remove(u"trace"_q);
const auto noTrace = CheckRejected(
QJsonDocument(object).toJson(QJsonDocument::Compact),
u"a body without a trace"_q);
if (!noTrace.isEmpty()) {
return noTrace;
}
object = QJsonDocument::fromJson(Body(Successful())).object();
object.remove(u"transactions"_q);
return CheckRejected(
QJsonDocument(object).toJson(QJsonDocument::Compact),
u"a body without transactions"_q);
} },
{ u"emulate_parse_rejects_bad_fee"_q, [] {
const auto fees = std::vector<QString>{
u"x"_q,
u"-1"_q,
QString(),
};
for (const auto &fee : fees) {
const auto failure = CheckRejected(
Body(kAccount, fee, false, true, true, 0, 0),
u"total_fees \""_q + fee + u"\""_q);
if (!failure.isEmpty()) {
return failure;
}
}
auto root = Successful();
root.insert(u"total_fees"_q, 11);
return CheckRejected(Body(root), u"a numeric total_fees"_q);
} },
{ u"emulate_parse_rejects_error_body"_q, [] {
const auto name = u"api-04-emulateTrace.json"_q;
const auto bytes = ReadFixture(name);
if (bytes.isEmpty()) {
return u"fixture read failed: "_q + name;
}
const auto recorded = CheckRejected(bytes, u"the error body"_q);
if (!recorded.isEmpty()) {
return recorded;
}
const auto bad = std::vector<QByteArray>{
QByteArray("not json"),
QByteArray(""),
QByteArray("{"),
QByteArray("[]"),
QByteArray("{}"),
QByteArray("null"),
QByteArray("42"),
QByteArray("{\"trace\":{\"tx_hash\":\"root\"}}"),
QByteArray("{\"trace\":{\"tx_hash\":\"root\"},"
"\"transactions\":{\"root\":{}}}"),
};
for (const auto &json : bad) {
const auto failure = CheckRejected(json, u"a malformed body"_q);
if (!failure.isEmpty()) {
return failure;
}
}
return QString();
} },
};
}
} // namespace Gram::Tests