#include <test/jtx.h>
#include <test/jtx/WSClient.h>
#include <test/rpc/GRPCTestClientBase.h>

#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/jss.h>

namespace xrpl {
namespace test {

class AccountInfo_test : public beast::unit_test::suite
{
public:
    void
    testErrors()
    {
        testcase("Errors");
        using namespace jtx;
        Env env(*this);
        {
            // account_info with no account.
            auto const info = env.rpc("json", "account_info", "{ }");
            BEAST_EXPECT(info[jss::result][jss::error_message] == "Missing field 'account'.");
        }
        {
            // account_info with a malformed account string.
            auto const info = env.rpc(
                "json",
                "account_info",
                "{\"account\": "
                "\"n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV\"}");
            BEAST_EXPECT(info[jss::result][jss::error_code] == rpcACT_MALFORMED);
            BEAST_EXPECT(info[jss::result][jss::error_message] == "Account malformed.");
        }
        {
            // account_info with an account that's not in the ledger.
            Account const bogie{"bogie"};
            Json::Value params;
            params[jss::account] = bogie.human();
            auto const info = env.rpc("json", "account_info", to_string(params));
            BEAST_EXPECT(info[jss::result][jss::error_code] == rpcACT_NOT_FOUND);
            BEAST_EXPECT(info[jss::result][jss::error_message] == "Account not found.");
        }
        {
            // Cannot use a seed as account
            auto const info = env.rpc("json", "account_info", R"({"account": "foo"})");
            BEAST_EXPECT(info[jss::result][jss::error_code] == rpcACT_MALFORMED);
            BEAST_EXPECT(info[jss::result][jss::error_message] == "Account malformed.");
        }
        {
            // Cannot pass a non-string into the `account` param

            auto testInvalidAccountParam = [&](auto const& param) {
                Json::Value params;
                params[jss::account] = param;
                auto jrr = env.rpc("json", "account_info", 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));
        }
        {
            // Cannot pass a non-string into the `ident` param

            auto testInvalidIdentParam = [&](auto const& param) {
                Json::Value params;
                params[jss::ident] = param;
                auto jrr = env.rpc("json", "account_info", to_string(params))[jss::result];
                BEAST_EXPECT(jrr[jss::error] == "invalidParams");
                BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'ident'.");
            };

            testInvalidIdentParam(1);
            testInvalidIdentParam(1.1);
            testInvalidIdentParam(true);
            testInvalidIdentParam(Json::Value(Json::nullValue));
            testInvalidIdentParam(Json::Value(Json::objectValue));
            testInvalidIdentParam(Json::Value(Json::arrayValue));
        }
    }

    // Test the "signer_lists" argument in account_info.
    void
    testSignerLists()
    {
        testcase("Signer lists");
        using namespace jtx;
        Env env(*this);
        Account const alice{"alice"};
        env.fund(XRP(1000), alice);

        Json::Value withoutSigners;
        withoutSigners[jss::account] = alice.human();

        Json::Value withSigners;
        withSigners[jss::account] = alice.human();
        withSigners[jss::signer_lists] = true;

        // Alice has no SignerList yet.
        {
            // account_info without the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withoutSigners));
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            BEAST_EXPECT(!info[jss::result][jss::account_data].isMember(jss::signer_lists));
        }
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withSigners));
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            auto const& data = info[jss::result][jss::account_data];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 0);
        }

        // Give alice a SignerList.
        Account const bogie{"bogie"};

        Json::Value const smallSigners = signers(alice, 2, {{bogie, 3}});
        env(smallSigners);
        {
            // account_info without the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withoutSigners));
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            BEAST_EXPECT(!info[jss::result][jss::account_data].isMember(jss::signer_lists));
        }
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withSigners));
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            auto const& data = info[jss::result][jss::account_data];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 1);
            auto const& signers = signerLists[0u];
            BEAST_EXPECT(signers.isObject());
            BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 2);
            auto const& signerEntries = signers[sfSignerEntries.jsonName];
            BEAST_EXPECT(signerEntries.size() == 1);
            auto const& entry0 = signerEntries[0u][sfSignerEntry.jsonName];
            BEAST_EXPECT(entry0[sfSignerWeight.jsonName] == 3);
        }

        // Give alice a big signer list
        Account const demon{"demon"};
        Account const ghost{"ghost"};
        Account const haunt{"haunt"};
        Account const jinni{"jinni"};
        Account const phase{"phase"};
        Account const shade{"shade"};
        Account const spook{"spook"};

        Json::Value const bigSigners = signers(
            alice,
            4,
            {
                {bogie, 1},
                {demon, 1},
                {ghost, 1},
                {haunt, 1},
                {jinni, 1},
                {phase, 1},
                {shade, 1},
                {spook, 1},
            });
        env(bigSigners);
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withSigners));
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            auto const& data = info[jss::result][jss::account_data];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 1);
            auto const& signers = signerLists[0u];
            BEAST_EXPECT(signers.isObject());
            BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 4);
            auto const& signerEntries = signers[sfSignerEntries.jsonName];
            BEAST_EXPECT(signerEntries.size() == 8);
            for (unsigned i = 0u; i < 8; ++i)
            {
                auto const& entry = signerEntries[i][sfSignerEntry.jsonName];
                BEAST_EXPECT(entry.size() == 2);
                BEAST_EXPECT(entry.isMember(sfAccount.jsonName));
                BEAST_EXPECT(entry[sfSignerWeight.jsonName] == 1);
            }
        }
    }

    // Test the "signer_lists" argument in account_info, with api_version 2.
    void
    testSignerListsApiVersion2()
    {
        testcase("Signer lists APIv2");
        using namespace jtx;
        Env env{*this};
        Account const alice{"alice"};
        env.fund(XRP(1000), alice);

        Json::Value withoutSigners;
        withoutSigners[jss::api_version] = 2;
        withoutSigners[jss::account] = alice.human();

        Json::Value withSigners;
        withSigners[jss::api_version] = 2;
        withSigners[jss::account] = alice.human();
        withSigners[jss::signer_lists] = true;

        auto const withSignersAsString = std::string("{ ") + "\"api_version\": 2, \"account\": \"" +
            alice.human() + "\", " + "\"signer_lists\": asdfggh }";

        // Alice has no SignerList yet.
        {
            // account_info without the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withoutSigners));
            BEAST_EXPECT(info.isMember(jss::result));
            BEAST_EXPECT(!info[jss::result].isMember(jss::signer_lists));
        }
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withSigners));
            BEAST_EXPECT(info.isMember(jss::result));
            auto const& data = info[jss::result];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 0);
        }

        // Give alice a SignerList.
        Account const bogie{"bogie"};

        Json::Value const smallSigners = signers(alice, 2, {{bogie, 3}});
        env(smallSigners);
        {
            // account_info without the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withoutSigners));
            BEAST_EXPECT(info.isMember(jss::result));
            BEAST_EXPECT(!info[jss::result].isMember(jss::signer_lists));
        }
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withSigners));
            BEAST_EXPECT(info.isMember(jss::result));
            auto const& data = info[jss::result];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 1);
            auto const& signers = signerLists[0u];
            BEAST_EXPECT(signers.isObject());
            BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 2);
            auto const& signerEntries = signers[sfSignerEntries.jsonName];
            BEAST_EXPECT(signerEntries.size() == 1);
            auto const& entry0 = signerEntries[0u][sfSignerEntry.jsonName];
            BEAST_EXPECT(entry0[sfSignerWeight.jsonName] == 3);
        }
        {
            // account_info with "signer_lists" as not bool should error out
            auto const info = env.rpc("json", "account_info", withSignersAsString);
            BEAST_EXPECT(info[jss::status] == "error");
            BEAST_EXPECT(info[jss::error] == "invalidParams");
        }

        // Give alice a big signer list
        Account const demon{"demon"};
        Account const ghost{"ghost"};
        Account const haunt{"haunt"};
        Account const jinni{"jinni"};
        Account const phase{"phase"};
        Account const shade{"shade"};
        Account const spook{"spook"};

        Json::Value const bigSigners = signers(
            alice,
            4,
            {
                {bogie, 1},
                {demon, 1},
                {ghost, 1},
                {haunt, 1},
                {jinni, 1},
                {phase, 1},
                {shade, 1},
                {spook, 1},
            });
        env(bigSigners);
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json", "account_info", to_string(withSigners));
            BEAST_EXPECT(info.isMember(jss::result));
            auto const& data = info[jss::result];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 1);
            auto const& signers = signerLists[0u];
            BEAST_EXPECT(signers.isObject());
            BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 4);
            auto const& signerEntries = signers[sfSignerEntries.jsonName];
            BEAST_EXPECT(signerEntries.size() == 8);
            for (unsigned i = 0u; i < 8; ++i)
            {
                auto const& entry = signerEntries[i][sfSignerEntry.jsonName];
                BEAST_EXPECT(entry.size() == 2);
                BEAST_EXPECT(entry.isMember(sfAccount.jsonName));
                BEAST_EXPECT(entry[sfSignerWeight.jsonName] == 1);
            }
        }
    }

    // Test the "signer_lists" argument in account_info, version 2 API.
    void
    testSignerListsV2()
    {
        testcase("Signer lists v2");
        using namespace jtx;
        Env env(*this);
        Account const alice{"alice"};
        env.fund(XRP(1000), alice);

        auto const withoutSigners = std::string("{ ") +
            "\"jsonrpc\": \"2.0\", "
            "\"ripplerpc\": \"2.0\", "
            "\"id\": 5, "
            "\"method\": \"account_info\", "
            "\"params\": { "
            "\"account\": \"" +
            alice.human() + "\"}}";

        auto const withSigners = std::string("{ ") +
            "\"jsonrpc\": \"2.0\", "
            "\"ripplerpc\": \"2.0\", "
            "\"id\": 6, "
            "\"method\": \"account_info\", "
            "\"params\": { "
            "\"account\": \"" +
            alice.human() + "\", " + "\"signer_lists\": true }}";
        // Alice has no SignerList yet.
        {
            // account_info without the "signer_lists" argument.
            auto const info = env.rpc("json2", withoutSigners);
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            BEAST_EXPECT(!info[jss::result][jss::account_data].isMember(jss::signer_lists));
            BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5);
        }
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json2", withSigners);
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            auto const& data = info[jss::result][jss::account_data];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 0);
            BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 6);
        }
        {
            // Do both of the above as a batch job
            auto const info = env.rpc("json2", '[' + withoutSigners + ", " + withSigners + ']');
            BEAST_EXPECT(
                info[0u].isMember(jss::result) &&
                info[0u][jss::result].isMember(jss::account_data));
            BEAST_EXPECT(!info[0u][jss::result][jss::account_data].isMember(jss::signer_lists));
            BEAST_EXPECT(info[0u].isMember(jss::jsonrpc) && info[0u][jss::jsonrpc] == "2.0");
            BEAST_EXPECT(info[0u].isMember(jss::ripplerpc) && info[0u][jss::ripplerpc] == "2.0");
            BEAST_EXPECT(info[0u].isMember(jss::id) && info[0u][jss::id] == 5);

            BEAST_EXPECT(
                info[1u].isMember(jss::result) &&
                info[1u][jss::result].isMember(jss::account_data));
            auto const& data = info[1u][jss::result][jss::account_data];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 0);
            BEAST_EXPECT(info[1u].isMember(jss::jsonrpc) && info[1u][jss::jsonrpc] == "2.0");
            BEAST_EXPECT(info[1u].isMember(jss::ripplerpc) && info[1u][jss::ripplerpc] == "2.0");
            BEAST_EXPECT(info[1u].isMember(jss::id) && info[1u][jss::id] == 6);
        }

        // Give alice a SignerList.
        Account const bogie{"bogie"};

        Json::Value const smallSigners = signers(alice, 2, {{bogie, 3}});
        env(smallSigners);
        {
            // account_info without the "signer_lists" argument.
            auto const info = env.rpc("json2", withoutSigners);
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            BEAST_EXPECT(!info[jss::result][jss::account_data].isMember(jss::signer_lists));
            BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5);
        }
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json2", withSigners);
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            auto const& data = info[jss::result][jss::account_data];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 1);
            auto const& signers = signerLists[0u];
            BEAST_EXPECT(signers.isObject());
            BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 2);
            auto const& signerEntries = signers[sfSignerEntries.jsonName];
            BEAST_EXPECT(signerEntries.size() == 1);
            auto const& entry0 = signerEntries[0u][sfSignerEntry.jsonName];
            BEAST_EXPECT(entry0[sfSignerWeight.jsonName] == 3);
            BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 6);
        }

        // Give alice a big signer list
        Account const demon{"demon"};
        Account const ghost{"ghost"};
        Account const haunt{"haunt"};
        Account const jinni{"jinni"};
        Account const phase{"phase"};
        Account const shade{"shade"};
        Account const spook{"spook"};

        Json::Value const bigSigners = signers(
            alice,
            4,
            {
                {bogie, 1},
                {demon, 1},
                {ghost, 1},
                {haunt, 1},
                {jinni, 1},
                {phase, 1},
                {shade, 1},
                {spook, 1},
            });
        env(bigSigners);
        {
            // account_info with the "signer_lists" argument.
            auto const info = env.rpc("json2", withSigners);
            BEAST_EXPECT(
                info.isMember(jss::result) && info[jss::result].isMember(jss::account_data));
            auto const& data = info[jss::result][jss::account_data];
            BEAST_EXPECT(data.isMember(jss::signer_lists));
            auto const& signerLists = data[jss::signer_lists];
            BEAST_EXPECT(signerLists.isArray());
            BEAST_EXPECT(signerLists.size() == 1);
            auto const& signers = signerLists[0u];
            BEAST_EXPECT(signers.isObject());
            BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 4);
            auto const& signerEntries = signers[sfSignerEntries.jsonName];
            BEAST_EXPECT(signerEntries.size() == 8);
            for (unsigned i = 0u; i < 8; ++i)
            {
                auto const& entry = signerEntries[i][sfSignerEntry.jsonName];
                BEAST_EXPECT(entry.size() == 2);
                BEAST_EXPECT(entry.isMember(sfAccount.jsonName));
                BEAST_EXPECT(entry[sfSignerWeight.jsonName] == 1);
            }
            BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0");
            BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 6);
        }
    }

    void
    testAccountFlags(FeatureBitset const& features)
    {
        testcase("Account flags");
        using namespace jtx;

        Env env(*this, features);
        Account const alice{"alice"};
        Account const bob{"bob"};
        env.fund(XRP(1000), alice, bob);

        auto getAccountFlag = [&env](std::string_view fName, Account const& account) {
            Json::Value params;
            params[jss::account] = account.human();
            auto const info = env.rpc("json", "account_info", to_string(params));
            auto const name = std::string(fName);

            std::optional<bool> res;
            if (info[jss::result][jss::status] == "success" &&
                info[jss::result][jss::account_flags].isMember(name))
                res.emplace(info[jss::result][jss::account_flags][name].asBool());

            return res;
        };

        static constexpr std::array<std::pair<std::string_view, std::uint32_t>, 7> asFlags{
            {{"defaultRipple", asfDefaultRipple},
             {"depositAuth", asfDepositAuth},
             {"disallowIncomingXRP", asfDisallowXRP},
             {"globalFreeze", asfGlobalFreeze},
             {"noFreeze", asfNoFreeze},
             {"requireAuthorization", asfRequireAuth},
             {"requireDestinationTag", asfRequireDest}}};

        for (auto& asf : asFlags)
        {
            // Clear a flag and check that account_info returns results
            // as expected
            env(fclear(alice, asf.second));
            env.close();
            auto const f1 = getAccountFlag(asf.first, alice);
            BEAST_EXPECT(f1.has_value());
            BEAST_EXPECT(!f1.value());  // NOLINT(bugprone-unchecked-optional-access)

            // Set a flag and check that account_info returns results
            // as expected
            env(fset(alice, asf.second));
            env.close();
            auto const f2 = getAccountFlag(asf.first, alice);
            BEAST_EXPECT(f2.has_value());
            BEAST_EXPECT(f2.value());  // NOLINT(bugprone-unchecked-optional-access)
        }

        static constexpr std::array<std::pair<std::string_view, std::uint32_t>, 4>
            disallowIncomingFlags{
                {{"disallowIncomingCheck", asfDisallowIncomingCheck},
                 {"disallowIncomingNFTokenOffer", asfDisallowIncomingNFTokenOffer},
                 {"disallowIncomingPayChan", asfDisallowIncomingPayChan},
                 {"disallowIncomingTrustline", asfDisallowIncomingTrustline}}};

        for (auto& asf : disallowIncomingFlags)
        {
            // Clear a flag and check that account_info returns results
            // as expected
            env(fclear(alice, asf.second));
            env.close();
            auto const f1 = getAccountFlag(asf.first, alice);
            BEAST_EXPECT(f1.has_value());
            BEAST_EXPECT(!f1.value());  // NOLINT(bugprone-unchecked-optional-access)

            // Set a flag and check that account_info returns results
            // as expected
            env(fset(alice, asf.second));
            env.close();
            auto const f2 = getAccountFlag(asf.first, alice);
            BEAST_EXPECT(f2.has_value());
            BEAST_EXPECT(f2.value());  // NOLINT(bugprone-unchecked-optional-access)
        }

        static constexpr std::pair<std::string_view, std::uint32_t> allowTrustLineClawbackFlag{
            "allowTrustLineClawback", asfAllowTrustLineClawback};

        if (features[featureClawback])
        {
            // must use bob's account because alice has noFreeze set
            auto const f1 = getAccountFlag(allowTrustLineClawbackFlag.first, bob);
            BEAST_EXPECT(f1.has_value());
            BEAST_EXPECT(!f1.value());  // NOLINT(bugprone-unchecked-optional-access)

            // Set allowTrustLineClawback
            env(fset(bob, allowTrustLineClawbackFlag.second));
            env.close();
            auto const f2 = getAccountFlag(allowTrustLineClawbackFlag.first, bob);
            BEAST_EXPECT(f2.has_value());
            BEAST_EXPECT(f2.value());  // NOLINT(bugprone-unchecked-optional-access)
        }
        else
        {
            BEAST_EXPECT(!getAccountFlag(allowTrustLineClawbackFlag.first, bob));
        }

        static constexpr std::pair<std::string_view, std::uint32_t> allowTrustLineLockingFlag{
            "allowTrustLineLocking", asfAllowTrustLineLocking};

        if (features[featureTokenEscrow])
        {
            auto const f1 = getAccountFlag(allowTrustLineLockingFlag.first, bob);
            BEAST_EXPECT(f1.has_value());
            BEAST_EXPECT(!f1.value());  // NOLINT(bugprone-unchecked-optional-access)

            // Set allowTrustLineLocking
            env(fset(bob, allowTrustLineLockingFlag.second));
            env.close();
            auto const f2 = getAccountFlag(allowTrustLineLockingFlag.first, bob);
            BEAST_EXPECT(f2.has_value());
            BEAST_EXPECT(f2.value());  // NOLINT(bugprone-unchecked-optional-access)
        }
        else
        {
            BEAST_EXPECT(!getAccountFlag(allowTrustLineLockingFlag.first, bob));
        }
    }

    void
    run() override
    {
        testErrors();
        testSignerLists();
        testSignerListsApiVersion2();
        testSignerListsV2();

        FeatureBitset const allFeatures{xrpl::test::jtx::testable_amendments()};
        testAccountFlags(allFeatures);
        testAccountFlags(allFeatures - featureClawback);
        testAccountFlags(allFeatures - featureClawback - featureTokenEscrow);
    }
};

BEAST_DEFINE_TESTSUITE(AccountInfo, rpc, xrpl);

}  // namespace test
}  // namespace xrpl
