Files
rippled/src/test/rpc/AccountOffers_test.cpp
Alex Kremer 57e4cbbcd9 refactor: Add simple clang-tidy readability checks (#6556)
This change enables the following clang-tidy checks:
-  readability-avoid-nested-conditional-operator,
-  readability-avoid-return-with-void-value,
-  readability-braces-around-statements,
-  readability-const-return-type,
-  readability-container-contains,
-  readability-container-size-empty,
-  readability-else-after-return,
-  readability-make-member-function-const,
-  readability-redundant-casting,
-  readability-redundant-inline-specifier,
-  readability-redundant-member-init,
-  readability-redundant-string-init,
-  readability-reference-to-constructed-temporary,
-  readability-static-definition
2026-03-18 16:41:49 +00:00

293 lines
11 KiB
C++

#include <test/jtx.h>
#include <xrpl/protocol/jss.h>
namespace xrpl {
namespace test {
class AccountOffers_test : public beast::unit_test::suite
{
public:
// test helper
static bool
checkMarker(Json::Value const& val)
{
return val.isMember(jss::marker) && val[jss::marker].isString() &&
!val[jss::marker].asString().empty();
}
void
testNonAdminMinLimit()
{
testcase("Non-Admin Min Limit");
using namespace jtx;
Env env{*this, envconfig(no_admin)};
Account const gw("G1");
auto const USD_gw = gw["USD"];
Account const bob("bob");
auto const USD_bob = bob["USD"];
env.fund(XRP(10000), gw, bob);
env.trust(USD_gw(1000), bob);
// this is to provide some USD from gw in the
// bob account so that it can rightly
// make offers that give those USDs
env(pay(gw, bob, USD_gw(10)));
unsigned const offer_count = 12u;
for (auto i = 0u; i < offer_count; i++)
{
Json::Value jvo = offer(bob, XRP(100 + i), USD_gw(1));
jvo[sfExpiration.fieldName] = 10000000u;
env(jvo);
}
// make non-limited RPC call
auto const jro_nl = env.rpc("account_offers", bob.human())[jss::result][jss::offers];
BEAST_EXPECT(checkArraySize(jro_nl, offer_count));
// now make a low-limit query, should get "corrected"
// to a min of 10 results with a marker set since there
// are more than 10 total
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = 1u;
auto const jrr_l =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l = jrr_l[jss::offers];
BEAST_EXPECT(checkMarker(jrr_l));
// 9u is the expected size, since one account object is a trustline
BEAST_EXPECT(checkArraySize(jro_l, 9u));
}
void
testSequential(bool asAdmin)
{
testcase(std::string("Sequential - ") + (asAdmin ? "admin" : "non-admin"));
using namespace jtx;
Env env{*this, asAdmin ? envconfig() : envconfig(no_admin)};
Account const gw("G1");
auto const USD_gw = gw["USD"];
Account const bob("bob");
auto const USD_bob = bob["USD"];
env.fund(XRP(10000), gw, bob);
env.trust(USD_gw(1000), bob);
// this is to provide some USD from gw in the
// bob account so that it can rightly
// make offers that give those USDs
env(pay(gw, bob, USD_gw(10)));
env(offer(bob, XRP(100), USD_bob(1)));
env(offer(bob, XRP(200), USD_gw(2)));
env(offer(bob, XRP(30), USD_gw(6)));
// make the RPC call
auto const jroOuter = env.rpc("account_offers", bob.human())[jss::result][jss::offers];
if (BEAST_EXPECT(checkArraySize(jroOuter, 3u)))
{
// Note that the returned offers are sorted by index, not by
// order of insertion or by sequence number. There is no
// guarantee that their order will not change in the future
// if the sequence numbers or the account IDs change.
BEAST_EXPECT(jroOuter[0u][jss::quality] == "100000000");
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::issuer] == gw.human());
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "2");
BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "200000000");
BEAST_EXPECT(jroOuter[1u][jss::quality] == "100000000");
BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::issuer] == bob.human());
BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::value] == "1");
BEAST_EXPECT(jroOuter[1u][jss::taker_pays] == "100000000");
BEAST_EXPECT(jroOuter[2u][jss::quality] == "5000000");
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::issuer] == gw.human());
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "6");
BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "30000000");
}
{
// now make a limit (= 1) query for the same data
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = 1u;
auto const jrr_l_1 =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l_1 = jrr_l_1[jss::offers];
// there is a difference in the validation of the limit param
// between admin and non-admin requests. with admin requests, the
// limit parameter is NOT subject to sane defaults, but with a
// non-admin there are pre-configured limit ranges applied. That's
// why we have different BEAST_EXPECT()s here for the two scenarios
BEAST_EXPECT(checkArraySize(jro_l_1, asAdmin ? 1u : 3u));
BEAST_EXPECT(asAdmin ? checkMarker(jrr_l_1) : (!jrr_l_1.isMember(jss::marker)));
if (asAdmin)
{
BEAST_EXPECT(jroOuter[0u] == jro_l_1[0u]);
// second item...with previous marker passed
jvParams[jss::marker] = jrr_l_1[jss::marker];
auto const jrr_l_2 =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l_2 = jrr_l_2[jss::offers];
BEAST_EXPECT(checkMarker(jrr_l_2));
BEAST_EXPECT(checkArraySize(jro_l_2, 1u));
BEAST_EXPECT(jroOuter[1u] == jro_l_2[0u]);
// last item...with previous marker passed
jvParams[jss::marker] = jrr_l_2[jss::marker];
jvParams[jss::limit] = 10u;
auto const jrr_l_3 =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
auto const& jro_l_3 = jrr_l_3[jss::offers];
BEAST_EXPECT(!jrr_l_3.isMember(jss::marker));
BEAST_EXPECT(checkArraySize(jro_l_3, 1u));
BEAST_EXPECT(jroOuter[2u] == jro_l_3[0u]);
}
else
{
BEAST_EXPECT(jroOuter == jro_l_1);
}
}
{
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = 0u;
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr.isMember(jss::error_message));
}
}
void
testBadInput()
{
testcase("Bad input");
using namespace jtx;
Env env(*this);
Account const gw("G1");
auto const USD_gw = gw["USD"];
Account const bob("bob");
auto const USD_bob = bob["USD"];
env.fund(XRP(10000), gw, bob);
env.trust(USD_gw(1000), bob);
{
// no account field
auto const jrr = env.rpc("account_offers");
BEAST_EXPECT(jrr[jss::error] == "badSyntax");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Syntax error.");
}
{
// test account non-string
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc("json", "account_offers", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
{
// empty string account
Json::Value jvParams;
jvParams[jss::account] = "";
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "actMalformed");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Account malformed.");
}
{
// bogus account value
auto const jrr = env.rpc("account_offers", Account("bogus").human())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "actNotFound");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Account not found.");
}
{
// bad limit
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::limit] = "0"; // NOT an integer
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not unsigned integer.");
}
{
// invalid marker
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::marker] = "NOT_A_MARKER";
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECTS(
jrr[jss::error_message] == "Invalid field 'marker'.", jrr.toStyledString());
}
{
// invalid marker - not a string
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::marker] = 1;
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not string.");
}
{
// ask for a bad ledger index
Json::Value jvParams;
jvParams[jss::account] = bob.human();
jvParams[jss::ledger_index] = 10u;
auto const jrr =
env.rpc("json", "account_offers", jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
}
void
run() override
{
testSequential(true);
testSequential(false);
testBadInput();
testNonAdminMinLimit();
}
};
BEAST_DEFINE_TESTSUITE(AccountOffers, rpc, xrpl);
} // namespace test
} // namespace xrpl