#include "data/AmendmentCenter.hpp" #include "data/Types.hpp" #include "rpc/Errors.hpp" #include "rpc/common/AnyHandler.hpp" #include "rpc/common/Types.hpp" #include "rpc/handlers/AccountInfo.hpp" #include "util/HandlerBaseTestFixture.hpp" #include "util/MockAmendmentCenter.hpp" #include "util/NameGenerator.hpp" #include "util/TestObject.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace rpc; using namespace data; namespace json = boost::json; using namespace testing; namespace { constexpr auto kAccount = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; constexpr auto kAccount1 = "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW"; constexpr auto kAccount2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"; constexpr auto kLedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; constexpr auto kIndex1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"; } // namespace struct RPCAccountInfoHandlerTest : HandlerBaseTest { RPCAccountInfoHandlerTest() { backend_->setRange(10, 30); } protected: StrictMockAmendmentCenterSharedPtr mockAmendmentCenterPtr_; }; struct AccountInfoParamTestCaseBundle { std::string testName; std::string testJson; std::string expectedError; std::string expectedErrorMessage; }; // parameterized test cases for parameters check struct AccountInfoParameterTest : RPCAccountInfoHandlerTest, WithParamInterface {}; static auto generateTestValuesForParametersTest() { return std::vector{ AccountInfoParamTestCaseBundle{ .testName = "MissingAccountAndIdent", .testJson = R"JSON({})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Missing field 'account'." }, AccountInfoParamTestCaseBundle{ .testName = "AccountNotString", .testJson = R"JSON({"account": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "accountNotString" }, AccountInfoParamTestCaseBundle{ .testName = "AccountInvalid", .testJson = R"JSON({"account": "xxx"})JSON", .expectedError = "actMalformed", .expectedErrorMessage = "accountMalformed" }, AccountInfoParamTestCaseBundle{ .testName = "IdentNotString", .testJson = R"JSON({"ident": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "identNotString" }, AccountInfoParamTestCaseBundle{ .testName = "IdentInvalid", .testJson = R"JSON({"ident": "xxx"})JSON", .expectedError = "actMalformed", .expectedErrorMessage = "identMalformed" }, AccountInfoParamTestCaseBundle{ .testName = "SignerListsInvalid", .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "signer_lists": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, AccountInfoParamTestCaseBundle{ .testName = "LedgerHashInvalid", .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": "1"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashMalformed" }, AccountInfoParamTestCaseBundle{ .testName = "LedgerHashNotString", .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_hash": 1})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledger_hashNotString" }, AccountInfoParamTestCaseBundle{ .testName = "LedgerIndexInvalid", .testJson = R"JSON({"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "ledger_index": "a"})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledgerIndexMalformed" }, }; } INSTANTIATE_TEST_CASE_P( RPCAccountInfoGroup1, AccountInfoParameterTest, ValuesIn(generateTestValuesForParametersTest()), tests::util::kNameGenerator ); TEST_P(AccountInfoParameterTest, InvalidParams) { auto const testBundle = GetParam(); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; auto const req = json::parse(testBundle.testJson); auto const output = handler.process(req, Context{.yield = yield, .apiVersion = 2}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), testBundle.expectedError); EXPECT_EQ(err.at("error_message").as_string(), testBundle.expectedErrorMessage); }); } TEST_F(AccountInfoParameterTest, ApiV1SignerListIsNotBool) { static constexpr auto kReqJson = R"JSON( {"ident": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "signer_lists": 1} )JSON"; EXPECT_CALL(*backend_, fetchLedgerBySequence); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; auto const req = json::parse(kReqJson); auto const output = handler.process(req, Context{.yield = yield, .apiVersion = 1}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); }); } TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaIntSequence) { // return empty ledgerHeader EXPECT_CALL(*backend_, fetchLedgerBySequence(30, _)) .WillOnce(Return(std::optional{})); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}", "ledger_index": 30 }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); }); } TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaStringSequence) { // return empty ledgerHeader EXPECT_CALL(*backend_, fetchLedgerBySequence(30, _)).WillOnce(Return(std::nullopt)); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}", "ledger_index": "30" }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); }); } TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaHash) { // return empty ledgerHeader EXPECT_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLedgerHash}, _)) .WillOnce(Return(std::optional{})); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}", "ledger_hash": "{}" }})JSON", kAccount, kLedgerHash ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); }); } TEST_F(RPCAccountInfoHandlerTest, AccountNotExist) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); ON_CALL(*backend_, doFetchLedgerObject).WillByDefault(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObject); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}" }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "actNotFound"); EXPECT_EQ(err.at("error_message").as_string(), "Account not found."); }); } TEST_F(RPCAccountInfoHandlerTest, AccountInvalid) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); // return a valid ledger object but not account root ON_CALL(*backend_, doFetchLedgerObject) .WillByDefault(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0))); EXPECT_CALL(*backend_, doFetchLedgerObject); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}" }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "dbDeserialization"); EXPECT_EQ(err.at("error_message").as_string(), "Database deserialization error."); }); } TEST_F(RPCAccountInfoHandlerTest, SignerListsInvalid) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject(kAccount, 0, 2, 200, 2, kIndex1, 2); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); auto signersKey = ripple::keylet::signers(account).key; ON_CALL(*backend_, doFetchLedgerObject(signersKey, 30, _)) .WillByDefault(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0))); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}", "signer_lists": true }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "dbDeserialization"); EXPECT_EQ(err.at("error_message").as_string(), "Database deserialization error."); }); } TEST_F(RPCAccountInfoHandlerTest, SignerListsTrueV2) { auto const expectedOutput = fmt::format( R"JSON({{ "account_data": {{ "Account": "{}", "Balance": "200", "Flags": 0, "LedgerEntryType": "AccountRoot", "OwnerCount": 2, "PreviousTxnID": "{}", "PreviousTxnLgrSeq": 2, "Sequence": 2, "TransferRate": 0, "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" }}, "signer_lists": [ {{ "Flags": 0, "LedgerEntryType": "SignerList", "OwnerNode": "0", "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", "PreviousTxnLgrSeq": 0, "SignerEntries": [ {{ "SignerEntry": {{ "Account": "{}", "SignerWeight": 1 }} }}, {{ "SignerEntry": {{ "Account": "{}", "SignerWeight": 1 }} }} ], "SignerListID": 0, "SignerQuorum": 2, "index": "A9C28A28B85CD533217F5C0A0C7767666B093FA58A0F2D80026FCC4CD932DDC7" }} ], "account_flags": {{ "defaultRipple": false, "depositAuth": false, "disableMasterKey": false, "disallowIncomingXRP": false, "globalFreeze": false, "noFreeze": false, "passwordSpent": false, "requireAuthorization": false, "requireDestinationTag": false }}, "ledger_hash": "{}", "ledger_index": 30, "validated": true }})JSON", kAccount, kIndex1, kAccount1, kAccount2, kLedgerHash ); auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject(kAccount, 0, 2, 200, 2, kIndex1, 2); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); auto signersKey = ripple::keylet::signers(account).key; ON_CALL(*backend_, doFetchLedgerObject(signersKey, 30, _)) .WillByDefault( Return(createSignerLists({{kAccount1, 1}, {kAccount2, 1}}).getSerializer().peekData()) ); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}", "signer_lists": true }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{.yield = yield, .apiVersion = 2}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(expectedOutput)); }); } TEST_F(RPCAccountInfoHandlerTest, SignerListsTrueV1) { auto const expectedOutput = fmt::format( R"JSON({{ "account_data": {{ "Account": "{}", "Balance": "200", "Flags": 0, "LedgerEntryType": "AccountRoot", "OwnerCount": 2, "PreviousTxnID": "{}", "PreviousTxnLgrSeq": 2, "Sequence": 2, "TransferRate": 0, "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8", "signer_lists": [ {{ "Flags": 0, "LedgerEntryType": "SignerList", "OwnerNode": "0", "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", "PreviousTxnLgrSeq": 0, "SignerEntries": [ {{ "SignerEntry": {{ "Account": "{}", "SignerWeight": 1 }} }}, {{ "SignerEntry": {{ "Account": "{}", "SignerWeight": 1 }} }} ], "SignerListID": 0, "SignerQuorum": 2, "index": "A9C28A28B85CD533217F5C0A0C7767666B093FA58A0F2D80026FCC4CD932DDC7" }} ] }}, "account_flags": {{ "defaultRipple": false, "depositAuth": false, "disableMasterKey": false, "disallowIncomingXRP": false, "globalFreeze": false, "noFreeze": false, "passwordSpent": false, "requireAuthorization": false, "requireDestinationTag": false }}, "ledger_hash": "{}", "ledger_index": 30, "validated": true }})JSON", kAccount, kIndex1, kAccount1, kAccount2, kLedgerHash ); auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject(kAccount, 0, 2, 200, 2, kIndex1, 2); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); auto signersKey = ripple::keylet::signers(account).key; ON_CALL(*backend_, doFetchLedgerObject(signersKey, 30, _)) .WillByDefault( Return(createSignerLists({{kAccount1, 1}, {kAccount2, 1}}).getSerializer().peekData()) ); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}", "signer_lists": true }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{.yield = yield, .apiVersion = 1}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(expectedOutput)); }); } TEST_F(RPCAccountInfoHandlerTest, Flags) { auto const expectedOutput = fmt::format( R"JSON({{ "account_data": {{ "Account": "{}", "Balance": "200", "Flags": 33488896, "LedgerEntryType": "AccountRoot", "OwnerCount": 2, "PreviousTxnID": "{}", "PreviousTxnLgrSeq": 2, "Sequence": 2, "TransferRate": 0, "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" }}, "account_flags": {{ "defaultRipple": true, "depositAuth": true, "disableMasterKey": true, "disallowIncomingXRP": true, "globalFreeze": true, "noFreeze": true, "passwordSpent": true, "requireAuthorization": true, "requireDestinationTag": true }}, "ledger_hash": "{}", "ledger_index": 30, "validated": true }})JSON", kAccount, kIndex1, kLedgerHash ); auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject( kAccount, ripple::lsfDefaultRipple | ripple::lsfGlobalFreeze | ripple::lsfRequireDestTag | ripple::lsfRequireAuth | ripple::lsfDepositAuth | ripple::lsfDisableMaster | ripple::lsfDisallowXRP | ripple::lsfNoFreeze | ripple::lsfPasswordSpent, 2, 200, 2, kIndex1, 2 ); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}" }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(expectedOutput)); }); } TEST_F(RPCAccountInfoHandlerTest, IdentAndSignerListsFalse) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject(kAccount, 0, 2, 200, 2, kIndex1, 2); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject); static auto const kInput = json::parse( fmt::format( R"JSON({{ "ident": "{}" }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_TRUE(output); EXPECT_FALSE(output.result->as_object().contains("signer_lists")); }); } TEST_F(RPCAccountInfoHandlerTest, EmptySignerLists) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject(kAccount, 0, 2, 200, 2, kIndex1, 2); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(false)); auto signersKey = ripple::keylet::signers(account).key; ON_CALL(*backend_, doFetchLedgerObject(signersKey, 30, _)) .WillByDefault(Return(std::optional{})); // Once for signer object, once for keylet EXPECT_CALL(*backend_, doFetchLedgerObject).Times(2); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}", "signer_lists": true }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{.yield = yield, .apiVersion = 2}); ASSERT_TRUE(output); auto const& resultObj = output.result->as_object(); ASSERT_TRUE(resultObj.contains("signer_lists")); auto const& signerListsJson = resultObj.at("signer_lists"); EXPECT_TRUE(signerListsJson.is_array()); EXPECT_TRUE(signerListsJson.as_array().empty()); }); } TEST_F(RPCAccountInfoHandlerTest, DisallowIncoming) { auto const expectedOutput = fmt::format( R"JSON({{ "account_data": {{ "Account": "{}", "Balance": "200", "Flags": 1040121856, "LedgerEntryType": "AccountRoot", "OwnerCount": 2, "PreviousTxnID": "{}", "PreviousTxnLgrSeq": 2, "Sequence": 2, "TransferRate": 0, "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" }}, "account_flags": {{ "defaultRipple": true, "depositAuth": true, "disableMasterKey": true, "disallowIncomingXRP": true, "globalFreeze": true, "noFreeze": true, "passwordSpent": true, "requireAuthorization": true, "requireDestinationTag": true, "disallowIncomingCheck": true, "disallowIncomingNFTokenOffer": true, "disallowIncomingPayChan": true, "disallowIncomingTrustline": true }}, "ledger_hash": "{}", "ledger_index": 30, "validated": true }})JSON", kAccount, kIndex1, kLedgerHash ); auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject( kAccount, ripple::lsfDefaultRipple | ripple::lsfGlobalFreeze | ripple::lsfRequireDestTag | ripple::lsfRequireAuth | ripple::lsfDepositAuth | ripple::lsfDisableMaster | ripple::lsfDisallowXRP | ripple::lsfNoFreeze | ripple::lsfPasswordSpent | ripple::lsfDisallowIncomingNFTokenOffer | ripple::lsfDisallowIncomingCheck | ripple::lsfDisallowIncomingPayChan | ripple::lsfDisallowIncomingTrustline, 2, 200, 2, kIndex1, 2 ); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(true)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(false)); EXPECT_CALL(*backend_, doFetchLedgerObject); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}" }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(expectedOutput)); }); } TEST_F(RPCAccountInfoHandlerTest, AmendmentsEnabled) { auto const expectedOutput = fmt::format( R"JSON({{ "account_data": {{ "Account": "{}", "Balance": "200", "Flags": 3254714368, "LedgerEntryType": "AccountRoot", "OwnerCount": 2, "PreviousTxnID": "{}", "PreviousTxnLgrSeq": 2, "Sequence": 2, "TransferRate": 0, "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" }}, "account_flags": {{ "defaultRipple": true, "depositAuth": true, "disableMasterKey": true, "disallowIncomingXRP": true, "globalFreeze": true, "noFreeze": true, "passwordSpent": true, "requireAuthorization": true, "requireDestinationTag": true, "allowTrustLineClawback": true, "allowTrustLineLocking": true }}, "ledger_hash": "{}", "ledger_index": 30, "validated": true }})JSON", kAccount, kIndex1, kLedgerHash ); auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30); EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); ON_CALL(*backend_, fetchLedgerBySequence).WillByDefault(Return(ledgerHeader)); auto const account = getAccountIdWithString(kAccount); auto const accountKk = ripple::keylet::account(account).key; auto const accountRoot = createAccountRootObject( kAccount, ripple::lsfDefaultRipple | ripple::lsfGlobalFreeze | ripple::lsfRequireDestTag | ripple::lsfRequireAuth | ripple::lsfDepositAuth | ripple::lsfDisableMaster | ripple::lsfDisallowXRP | ripple::lsfNoFreeze | ripple::lsfPasswordSpent | ripple::lsfAllowTrustLineClawback | ripple::lsfAllowTrustLineLocking, 2, 200, 2, kIndex1, 2 ); ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _)) .WillOnce(Return(false)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _)) .WillOnce(Return(true)); EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _)) .WillOnce(Return(true)); EXPECT_CALL(*backend_, doFetchLedgerObject); static auto const kInput = json::parse( fmt::format( R"JSON({{ "account": "{}" }})JSON", kAccount ) ); auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}}; runSpawn([&](auto yield) { auto const output = handler.process(kInput, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(expectedOutput)); }); } TEST(RPCAccountInfoHandlerSpecTest, DeprecatedFields) { boost::json::value const json{ {"account", kAccount}, {"ident", kAccount}, {"ledger_index", 30}, {"ledger_hash", kLedgerHash}, {"ledger", "some"}, {"strict", true} }; auto const spec = AccountInfoHandler::spec(2); auto const warnings = spec.check(json); ASSERT_EQ(warnings.size(), 1); auto const& warning = warnings[0]; ASSERT_TRUE(warning.is_object()); auto const obj = warning.as_object(); ASSERT_TRUE(obj.contains("id")); ASSERT_TRUE(obj.contains("message")); EXPECT_EQ(obj.at("id").as_int64(), static_cast(WarningCode::WarnRpcDeprecated)); auto const& message = obj.at("message").as_string(); for (auto const& field : {"ident", "ledger", "strict"}) { EXPECT_NE(message.find(fmt::format("Field '{}' is deprecated", field)), std::string::npos) << message; } }