Account tx v1 api support (#874)

* Don't fail on ledger params for v1

* Different error on invalid ledger indexes for v1

* Allow forward and binary to be not bool for v1

* Minor fixes

* Fix tests

* Don't fail if input ledger index is out of range for v1

* Restore deleted test

* Fix comparison of integers with different signedness

* Updated default api version in README and example config
This commit is contained in:
Sergey Kuznetsov
2023-09-28 11:31:35 +01:00
committed by GitHub
parent 963685dd31
commit 6ca777ea96
7 changed files with 375 additions and 144 deletions

View File

@@ -224,16 +224,16 @@ a database in each region, and the Clio nodes in each region use their region's
This is effectively two systems.
Clio supports API versioning as [described here](https://xrpl.org/request-formatting.html#api-versioning).
It's possible to configure `minimum`, `maximum` and `default` version like so:
It's possible to configure `minimum`, `maximum` and `default` version like so:
```json
"api_version": {
"min": 1,
"max": 2,
"default": 2
"default": 1
}
```
All of the above are optional.
Clio will fallback to hardcoded defaults when not specified in the config file or configured values are outside
All of the above are optional.
Clio will fallback to hardcoded defaults when not specified in the config file or configured values are outside
of the minimum and maximum supported versions hardcoded in `src/rpc/common/APIVersion.h`.
> **Note:** See `example-config.json` for more details.

View File

@@ -111,8 +111,8 @@
// "ssl_cert_file" : "/full/path/to/cert.file",
// "ssl_key_file" : "/full/path/to/key.file"
"api_version": {
"min": 2,
"max": 2,
"default": 2 // Clio only supports API v2 and newer
"min": 1, // Minimum API version supported (could be 1 or 2)
"max": 2, // Maximum API version supported (could be 1 or 2, but >= min)
"default": 1 // Clio behaves the same as rippled by default
}
}

46
src/rpc/common/JsonBool.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <boost/json/value_to.hpp>
namespace rpc {
/**
* @brief A wrapper around bool that allows to convert from any JSON value
*/
struct JsonBool
{
bool value = false;
operator bool() const
{
return value;
}
};
inline JsonBool
tag_invoke(boost::json::value_to_tag<JsonBool> const&, boost::json::value const& jsonValue)
{
switch (jsonValue.kind())
{
case boost::json::kind::null:
return JsonBool{false};
case boost::json::kind::bool_:
return JsonBool{jsonValue.as_bool()};
case boost::json::kind::uint64:
[[fallthrough]];
case boost::json::kind::int64:
return JsonBool{jsonValue.as_int64() != 0};
case boost::json::kind::double_:
return JsonBool{jsonValue.as_double() != 0.0};
case boost::json::kind::string:
// Also should be `jsonValue.as_string() != "false"` but rippled doesn't do that. Anyway for v2 api we have
// bool validation
return JsonBool{!jsonValue.as_string().empty() && jsonValue.as_string()[0] != 0};
case boost::json::kind::array:
return JsonBool{!jsonValue.as_array().empty()};
case boost::json::kind::object:
return JsonBool{!jsonValue.as_object().empty()};
}
throw std::runtime_error("Invalid json value");
}
} // namespace rpc

View File

@@ -76,6 +76,18 @@ struct RpcSpec final
{
}
/**
* @brief Construct a full RPC request specification from another spec and additional fields.
*
* @param other The other spec to copy fields from
* @param additionalFields The additional fields to add to the spec
*/
RpcSpec(const RpcSpec& other, std::initializer_list<FieldSpec> additionalFields) : fields_{other.fields_}
{
for (auto& f : additionalFields)
fields_.push_back(std::move(f));
}
/**
* @brief Processos the passed JSON value using the stored field specs.
*

View File

@@ -72,27 +72,39 @@ AccountTxHandler::process(AccountTxHandler::Input input, Context const& ctx) con
if (input.ledgerIndexMin)
{
if (range->maxSequence < input.ledgerIndexMin || range->minSequence > input.ledgerIndexMin)
if (ctx.apiVersion > 1u &&
(input.ledgerIndexMin > range->maxSequence || input.ledgerIndexMin < range->minSequence))
{
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMinOutOfRange"}};
}
minIndex = *input.ledgerIndexMin;
if (static_cast<std::uint32_t>(*input.ledgerIndexMin) > minIndex)
minIndex = *input.ledgerIndexMin;
}
if (input.ledgerIndexMax)
{
if (range->maxSequence < input.ledgerIndexMax || range->minSequence > input.ledgerIndexMax)
if (ctx.apiVersion > 1u &&
(input.ledgerIndexMax > range->maxSequence || input.ledgerIndexMax < range->minSequence))
{
return Error{Status{RippledError::rpcLGR_IDX_MALFORMED, "ledgerSeqMaxOutOfRange"}};
}
maxIndex = *input.ledgerIndexMax;
if (static_cast<std::uint32_t>(*input.ledgerIndexMax) < maxIndex)
maxIndex = *input.ledgerIndexMax;
}
if (minIndex > maxIndex)
{
if (ctx.apiVersion == 1u)
return Error{Status{RippledError::rpcLGR_IDXS_INVALID}};
return Error{Status{RippledError::rpcINVALID_LGR_RANGE}};
}
if (input.ledgerHash || input.ledgerIndex || input.usingValidatedLedger)
{
// rippled does not have this check
if (input.ledgerIndexMax || input.ledgerIndexMin)
if (ctx.apiVersion > 1u && (input.ledgerIndexMax || input.ledgerIndexMin))
return Error{Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}};
auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq(
@@ -247,10 +259,10 @@ tag_invoke(boost::json::value_to_tag<AccountTxHandler::Input>, boost::json::valu
}
if (jsonObject.contains(JS(binary)))
input.binary = jsonObject.at(JS(binary)).as_bool();
input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
if (jsonObject.contains(JS(forward)))
input.forward = jsonObject.at(JS(forward)).as_bool();
input.forward = boost::json::value_to<JsonBool>(jsonObject.at(JS(forward)));
if (jsonObject.contains(JS(limit)))
input.limit = jsonObject.at(JS(limit)).as_int64();

View File

@@ -21,6 +21,7 @@
#include <data/BackendInterface.h>
#include <rpc/RPCHelpers.h>
#include <rpc/common/JsonBool.h>
#include <rpc/common/MetaProcessors.h>
#include <rpc/common/Modifiers.h>
#include <rpc/common/Types.h>
@@ -76,8 +77,8 @@ public:
std::optional<int32_t> ledgerIndexMin;
std::optional<int32_t> ledgerIndexMax;
bool usingValidatedLedger = false;
bool binary = false;
bool forward = false;
JsonBool binary{false};
JsonBool forward{false};
std::optional<uint32_t> limit;
std::optional<Marker> marker;
std::optional<ripple::TxType> transactionType;
@@ -92,14 +93,12 @@ public:
RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion) const
{
static auto const rpcSpec = RpcSpec{
static auto const rpcSpecForV1 = RpcSpec{
{JS(account), validation::Required{}, validation::AccountValidator},
{JS(ledger_hash), validation::Uint256HexStringValidator},
{JS(ledger_index), validation::LedgerIndexValidator},
{JS(ledger_index_min), validation::Type<int32_t>{}},
{JS(ledger_index_max), validation::Type<int32_t>{}},
{JS(binary), validation::Type<bool>{}},
{JS(forward), validation::Type<bool>{}},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
@@ -121,7 +120,14 @@ public:
},
};
return rpcSpec;
static auto const rpcSpec = RpcSpec{
rpcSpecForV1,
{
{JS(binary), validation::Type<bool>{}},
{JS(forward), validation::Type<bool>{}},
}};
return apiVersion == 1 ? rpcSpecForV1 : rpcSpec;
}
Result

View File

@@ -45,8 +45,9 @@ struct AccountTxParamTestCaseBundle
{
std::string testName;
std::string testJson;
std::string expectedError;
std::string expectedErrorMessage;
std::optional<std::string> expectedError;
std::optional<std::string> expectedErrorMessage;
std::uint32_t apiVersion = 2;
};
// parameterized test cases for parameters check
@@ -58,91 +59,102 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest, public WithParam
std::string
operator()(const testing::TestParamInfo<ParamType>& info) const
{
auto bundle = static_cast<AccountTxParamTestCaseBundle>(info.param);
return bundle.testName;
return info.param.testName;
}
};
};
static auto
generateTestValuesForParametersTest()
{
return std::vector<AccountTxParamTestCaseBundle>{
AccountTxParamTestCaseBundle{"MissingAccount", R"({})", "invalidParams", "Required field 'account' missing"},
AccountTxParamTestCaseBundle{
"BinaryNotBool",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"ForwardNotBool",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"ledger_index_minNotInt",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"ledger_index_maxNotInt",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"ledger_indexInvalid",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})",
"invalidParams",
"ledgerIndexMalformed"},
AccountTxParamTestCaseBundle{
"ledger_hashInvalid",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})",
"invalidParams",
"ledger_hashMalformed"},
AccountTxParamTestCaseBundle{
"ledger_hashNotString",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})",
"invalidParams",
"ledger_hashNotString"},
AccountTxParamTestCaseBundle{
"limitNotInt",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"limitNegative",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"limitZero",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 0})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"MarkerNotObject",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": 101})",
"invalidParams",
"invalidMarker"},
AccountTxParamTestCaseBundle{
"MarkerMissingSeq",
R"({
static auto
generateTestValuesForParametersTest()
{
return std::vector<AccountTxParamTestCaseBundle>{
AccountTxParamTestCaseBundle{
"MissingAccount", R"({})", "invalidParams", "Required field 'account' missing"},
AccountTxParamTestCaseBundle{
"BinaryNotBool",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"BinaryNotBool_API_v1",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "binary": 1})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"ForwardNotBool",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"ForwardNotBool_API_v1",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "forward": 1})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"ledger_index_minNotInt",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_min": "x"})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"ledger_index_maxNotInt",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index_max": "x"})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"ledger_indexInvalid",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})",
"invalidParams",
"ledgerIndexMalformed"},
AccountTxParamTestCaseBundle{
"ledger_hashInvalid",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})",
"invalidParams",
"ledger_hashMalformed"},
AccountTxParamTestCaseBundle{
"ledger_hashNotString",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})",
"invalidParams",
"ledger_hashNotString"},
AccountTxParamTestCaseBundle{
"limitNotInt",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": "123"})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"limitNegative",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": -1})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"limitZero",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "limit": 0})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"MarkerNotObject",
R"({"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "marker": 101})",
"invalidParams",
"invalidMarker"},
AccountTxParamTestCaseBundle{
"MarkerMissingSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker": {"ledger": 123}
})",
"invalidParams",
"Required field 'seq' missing"},
AccountTxParamTestCaseBundle{
"MarkerMissingLedger",
R"({
"invalidParams",
"Required field 'seq' missing"},
AccountTxParamTestCaseBundle{
"MarkerMissingLedger",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker":{"seq": 123}
})",
"invalidParams",
"Required field 'ledger' missing"},
AccountTxParamTestCaseBundle{
"MarkerLedgerNotInt",
R"({
"invalidParams",
"Required field 'ledger' missing"},
AccountTxParamTestCaseBundle{
"MarkerLedgerNotInt",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker":
{
@@ -150,11 +162,11 @@ generateTestValuesForParametersTest()
"ledger": 1
}
})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"MarkerSeqNotInt",
R"({
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"MarkerSeqNotInt",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"marker":
{
@@ -162,77 +174,220 @@ generateTestValuesForParametersTest()
"seq": 1
}
})",
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"LedgerIndexMinLessThanMinSeq",
R"({
"invalidParams",
"Invalid parameters."},
AccountTxParamTestCaseBundle{
"LedgerIndexMinLessThanMinSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 9
})",
"lgrIdxMalformed",
"ledgerSeqMinOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxLargeThanMaxSeq",
R"({
"lgrIdxMalformed",
"ledgerSeqMinOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxLargeThanMaxSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 31
})",
"lgrIdxMalformed",
"ledgerSeqMaxOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxLessThanLedgerIndexMin",
R"({
"lgrIdxMalformed",
"ledgerSeqMaxOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxLargeThanMaxSeq_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 31
})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxSmallerThanMinSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 9
})",
"lgrIdxMalformed",
"ledgerSeqMaxOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxSmallerThanMinSeq_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 9
})",
"lgrIdxsInvalid",
"Ledger indexes invalid.",
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMinSmallerThanMinSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 9
})",
"lgrIdxMalformed",
"ledgerSeqMinOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMinSmallerThanMinSeq_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 9
})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMinLargerThanMaxSeq",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 31
})",
"lgrIdxMalformed",
"ledgerSeqMinOutOfRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMinLargerThanMaxSeq_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_min": 31
})",
"lgrIdxsInvalid",
"Ledger indexes invalid.",
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxLessThanLedgerIndexMin",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 11,
"ledger_index_min": 20
})",
"invalidLgrRange",
"Ledger range is invalid."},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndex",
R"({
"invalidLgrRange",
"Ledger range is invalid."},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxLessThanLedgerIndexMin_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 11,
"ledger_index_min": 20
})",
"lgrIdxsInvalid",
"Ledger indexes invalid.",
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndex",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_index": 10
})",
"invalidParams",
"containsLedgerSpecifierAndRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndexValidated",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"invalidParams",
"containsLedgerSpecifierAndRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndexValidated",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_index": "validated"
})",
"invalidParams",
"containsLedgerSpecifierAndRange"},
"invalidParams",
"containsLedgerSpecifierAndRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndex_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_index": 10
})",
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerHash",
fmt::format(
R"({{
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_hash": "{}"
}})",
LEDGERHASH),
"invalidParams",
"containsLedgerSpecifierAndRange"},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerHash_API_v1",
fmt::format(
R"({{
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_hash": "{}"
}})",
LEDGERHASH),
std::nullopt,
std::nullopt,
1u},
AccountTxParamTestCaseBundle{
"LedgerIndexMaxMinAndLedgerIndexValidated_API_v1",
R"({
"account":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_index_max": 20,
"ledger_index_min": 11,
"ledger_index": "validated"
})",
std::nullopt,
std::nullopt,
1u},
};
};
}
};
INSTANTIATE_TEST_CASE_P(
RPCAccountTxGroup1,
AccountTxParameterTest,
ValuesIn(generateTestValuesForParametersTest()),
ValuesIn(AccountTxParameterTest::generateTestValuesForParametersTest()),
AccountTxParameterTest::NameGenerator{});
TEST_P(AccountTxParameterTest, InvalidParams)
TEST_P(AccountTxParameterTest, CheckParams)
{
mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max
auto const testBundle = GetParam();
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
auto const req = json::parse(testBundle.testJson);
auto const output = handler.process(req, Context{yield});
ASSERT_FALSE(output);
auto* rawBackendPtr = static_cast<MockBackend*>(mockBackendPtr.get());
std::cout << "Before parse" << std::endl;
auto const req = json::parse(testBundle.testJson);
std::cout << "After parse" << std::endl;
if (testBundle.expectedError.has_value())
{
ASSERT_TRUE(testBundle.expectedErrorMessage.has_value());
auto const err = rpc::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), testBundle.expectedError);
EXPECT_EQ(err.at("error_message").as_string(), testBundle.expectedErrorMessage);
});
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
auto const output = handler.process(req, Context{.yield = yield, .apiVersion = testBundle.apiVersion});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.error());
EXPECT_EQ(err.at("error").as_string(), *testBundle.expectedError);
EXPECT_EQ(err.at("error_message").as_string(), *testBundle.expectedErrorMessage);
});
}
else
{
if (req.as_object().contains("ledger_hash"))
{
EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).WillOnce(testing::Return(ripple::LedgerHeader{}));
}
else if (req.as_object().contains("ledger_index"))
{
EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).WillOnce(testing::Return(ripple::LedgerHeader{}));
}
EXPECT_CALL(*rawBackendPtr, fetchAccountTransactions);
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{AccountTxHandler{mockBackendPtr}};
auto const output = handler.process(req, Context{.yield = yield, .apiVersion = testBundle.apiVersion});
EXPECT_TRUE(output);
});
}
}
namespace {