#include "data/Types.hpp" #include "rpc/CredentialHelpers.hpp" #include "rpc/Errors.hpp" #include "rpc/common/AnyHandler.hpp" #include "rpc/common/Types.hpp" #include "rpc/handlers/LedgerEntry.hpp" #include "util/HandlerBaseTestFixture.hpp" #include "util/NameGenerator.hpp" #include "util/TestObject.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 kIndex1 = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"; constexpr auto kAccount = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; constexpr auto kAccount2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"; constexpr auto kAccount3 = "rhzcyub9SbyZ4YF1JYskN5rLrTDUuLZG6D"; constexpr auto kRangeMin = 10; constexpr auto kRangeMax = 30; constexpr auto kLedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; constexpr auto kTokenId = "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA"; constexpr auto kNftId = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004"; constexpr auto kTxnId = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"; constexpr auto kCredentialType = "4B5943"; } // namespace struct RPCLedgerEntryTest : HandlerBaseTest { RPCLedgerEntryTest() { backend_->setRange(kRangeMin, kRangeMax); } }; struct ParamTestCaseBundle { std::string testName; std::string testJson; std::string expectedError; std::string expectedErrorMessage; }; // parameterized test cases for parameters check struct LedgerEntryParameterTest : public RPCLedgerEntryTest, public WithParamInterface {}; // TODO: because we extract the error generation from the handler to framework // the error messages need one round fine tuning static auto generateTestValuesForParametersTest() { return std::vector{ ParamTestCaseBundle{ .testName = "InvalidBinaryType", .testJson = R"JSON({ "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "binary": "invalid" })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, ParamTestCaseBundle{ .testName = "InvalidAccountRootFormat", .testJson = R"JSON({ "account_root": "invalid" })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "InvalidDidFormat", .testJson = R"JSON({ "did": "invalid" })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "InvalidAccountRootNotString", .testJson = R"JSON({ "account_root": 123 })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "account_rootNotString" }, ParamTestCaseBundle{ .testName = "InvalidLedgerIndex", .testJson = R"JSON({ "ledger_index": "wrong" })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ledgerIndexMalformed" }, ParamTestCaseBundle{ .testName = "UnknownOption", .testJson = R"JSON({})JSON", .expectedError = "invalidParams", .expectedErrorMessage = "No ledger_entry params provided." }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthType", .testJson = R"JSON({ "deposit_preauth": 123 })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthString", .testJson = R"JSON({ "deposit_preauth": "invalid" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthEmptyJson", .testJson = R"JSON({ "deposit_preauth": {} })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Required field 'owner' missing" }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthJsonWrongAccount", .testJson = R"JSON({ "deposit_preauth": { "owner": "invalid", "authorized": "invalid" } })JSON", .expectedError = "malformedOwner", .expectedErrorMessage = "Malformed owner." }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthJsonOwnerNotString", .testJson = R"JSON({ "deposit_preauth": { "owner": 123, "authorized": 123 } })JSON", .expectedError = "malformedOwner", .expectedErrorMessage = "Malformed owner." }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthJsonAuthorizedNotString", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized": 123 }} }})JSON", kAccount ), .expectedError = "invalidParams", .expectedErrorMessage = "authorizedNotString" }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthJsonAuthorizeCredentialsNotArray", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": "asdf" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "authorized_credentials not array" }, ParamTestCaseBundle{ .testName = "InvalidDepositPreauthJsonAuthorizeCredentialsMalformedString", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": ["C2F2A19C8D0D893D18F18FDCFE13A3ECB41767E48422DF07F2455CDA08FDF09B"] }} }})JSON", kAccount ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "authorized_credentials elements in array are not objects." }, ParamTestCaseBundle{ .testName = "DepositPreauthBothAuthAndAuthCredentialsDoesNotExists", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Must have one of authorized or authorized_credentials." }, ParamTestCaseBundle{ .testName = "DepositPreauthBothAuthAndAuthCredentialsExists", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized": "{}", "authorized_credentials": [ {{ "issuer": "{}", "credential_type": "{}" }} ] }} }})JSON", kAccount, kAccount2, kAccount3, kCredentialType ), .expectedError = "malformedRequest", .expectedErrorMessage = "Must have one of authorized or authorized_credentials." }, ParamTestCaseBundle{ .testName = "DepositPreauthEmptyAuthorizeCredentials", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ ] }} }})JSON", kAccount ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "Requires at least one element in authorized_credentials array." }, ParamTestCaseBundle{ .testName = "DepositPreauthAuthorizeCredentialsMissingCredentialType", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "issuer": "{}" }} ] }} }})JSON", kAccount, kAccount2 ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "Field 'CredentialType' is required but missing." }, ParamTestCaseBundle{ .testName = "DepositPreauthAuthorizeCredentialsMissingIssuer", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "credential_type": "{}" }} ] }} }})JSON", kAccount, kCredentialType ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "Field 'Issuer' is required but missing." }, ParamTestCaseBundle{ .testName = "DepositPreauthAuthorizeCredentialsIncorrectIssuerType", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "issuer": 123, "credential_type": "{}" }} ] }} }})JSON", kAccount, kCredentialType ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "issuer NotString" }, ParamTestCaseBundle{ .testName = "DepositPreauthAuthorizeCredentialsIncorrectCredentialType", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "issuer": "{}", "credential_type": 432 }} ] }} }})JSON", kAccount, kAccount2 ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "credential_type NotString" }, ParamTestCaseBundle{ .testName = "DepositPreauthAuthorizeCredentialsCredentialTypeNotHex", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "issuer": "{}", "credential_type": "hello world" }} ] }} }})JSON", kAccount, kAccount2 ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "credential_type NotHexString" }, ParamTestCaseBundle{ .testName = "DepositPreauthAuthorizeCredentialsCredentialTypeEmpty", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "issuer": "{}", "credential_type": "" }} ] }} }})JSON", kAccount, kAccount2 ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "credential_type is empty" }, ParamTestCaseBundle{ .testName = "DepositPreauthDuplicateAuthorizeCredentials", .testJson = fmt::format( R"JSON({{ "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "issuer": "{}", "credential_type": "{}" }}, {{ "issuer": "{}", "credential_type": "{}" }} ] }} }})JSON", kAccount, kAccount2, kCredentialType, kAccount2, kCredentialType ), .expectedError = "malformedAuthorizedCredentials", .expectedErrorMessage = "duplicates in credentials." }, ParamTestCaseBundle{ .testName = "InvalidTicketType", .testJson = R"JSON({ "ticket": 123 })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, ParamTestCaseBundle{ .testName = "InvalidTicketIndex", .testJson = R"JSON({ "ticket": "invalid" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidTicketEmptyJson", .testJson = R"JSON({ "ticket": {} })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Required field 'account' missing" }, ParamTestCaseBundle{ .testName = "InvalidTicketJsonAccountNotString", .testJson = R"JSON({ "ticket": { "account": 123, "ticket_seq": 123 } })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "accountNotString" }, ParamTestCaseBundle{ .testName = "InvalidTicketJsonAccountInvalid", .testJson = R"JSON({ "ticket": { "account": "123", "ticket_seq": 123 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "InvalidTicketJsonSeqNotInt", .testJson = fmt::format( R"JSON({{ "ticket": {{ "account": "{}", "ticket_seq": "123" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidOfferType", .testJson = R"JSON({ "offer": 123 })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, ParamTestCaseBundle{ .testName = "InvalidOfferIndex", .testJson = R"JSON({ "offer": "invalid" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidOfferEmptyJson", .testJson = R"JSON({ "offer": {} })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Required field 'account' missing" }, ParamTestCaseBundle{ .testName = "InvalidOfferJsonAccountNotString", .testJson = R"JSON({ "ticket": { "account": 123, "seq": 123 } })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "accountNotString" }, ParamTestCaseBundle{ .testName = "InvalidOfferJsonAccountInvalid", .testJson = R"JSON({ "ticket": { "account": "123", "seq": 123 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "InvalidOfferJsonSeqNotInt", .testJson = fmt::format( R"JSON({{ "offer": {{ "account": "{}", "seq": "123" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidEscrowType", .testJson = R"JSON({ "escrow": 123 })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, ParamTestCaseBundle{ .testName = "InvalidEscrowIndex", .testJson = R"JSON({ "escrow": "invalid" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidEscrowEmptyJson", .testJson = R"JSON({ "escrow": {} })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Required field 'owner' missing" }, ParamTestCaseBundle{ .testName = "InvalidEscrowJsonAccountNotString", .testJson = R"JSON({ "escrow": { "owner": 123, "seq": 123 } })JSON", .expectedError = "malformedOwner", .expectedErrorMessage = "Malformed owner." }, ParamTestCaseBundle{ .testName = "InvalidEscrowJsonAccountInvalid", .testJson = R"JSON({ "escrow": { "owner": "123", "seq": 123 } })JSON", .expectedError = "malformedOwner", .expectedErrorMessage = "Malformed owner." }, ParamTestCaseBundle{ .testName = "InvalidEscrowJsonSeqNotInt", .testJson = fmt::format( R"JSON({{ "escrow": {{ "owner": "{}", "seq": "123" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidRippleStateType", .testJson = R"JSON({ "ripple_state": "123" })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, ParamTestCaseBundle{ .testName = "InvalidRippleStateMissField", .testJson = R"JSON({ "ripple_state": { "currency": "USD" } })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Required field 'accounts' missing" }, ParamTestCaseBundle{ .testName = "InvalidRippleStateEmptyJson", .testJson = R"JSON({ "ripple_state": {} })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Required field 'accounts' missing" }, ParamTestCaseBundle{ .testName = "InvalidRippleStateOneAccount", .testJson = fmt::format( R"JSON({{ "ripple_state": {{ "accounts": ["{}"] }} }})JSON", kAccount ), .expectedError = "invalidParams", .expectedErrorMessage = "malformedAccounts" }, ParamTestCaseBundle{ .testName = "InvalidRippleStateSameAccounts", .testJson = fmt::format( R"JSON({{ "ripple_state": {{ "accounts": ["{}", "{}"], "currency": "USD" }} }})JSON", kAccount, kAccount ), .expectedError = "invalidParams", .expectedErrorMessage = "malformedAccounts" }, ParamTestCaseBundle{ .testName = "InvalidRippleStateWrongAccountsNotString", .testJson = fmt::format( R"JSON({{ "ripple_state": {{ "accounts": ["{}",123], "currency": "USD" }} }})JSON", kAccount ), .expectedError = "invalidParams", .expectedErrorMessage = "malformedAccounts" }, ParamTestCaseBundle{ .testName = "InvalidRippleStateWrongAccountsFormat", .testJson = fmt::format( R"JSON({{ "ripple_state": {{ "accounts": ["{}", "123"], "currency": "USD" }} }})JSON", kAccount ), .expectedError = "malformedAddress", .expectedErrorMessage = "malformedAddresses" }, ParamTestCaseBundle{ .testName = "InvalidRippleStateWrongCurrency", .testJson = fmt::format( R"JSON({{ "ripple_state": {{ "accounts": ["{}", "{}"], "currency": "XXXX" }} }})JSON", kAccount, kAccount2 ), .expectedError = "malformedCurrency", .expectedErrorMessage = "malformedCurrency" }, ParamTestCaseBundle{ .testName = "InvalidRippleStateWrongCurrencyNotString", .testJson = fmt::format( R"JSON({{ "ripple_state": {{ "accounts": ["{}", "{}"], "currency": 123 }} }})JSON", kAccount, kAccount2 ), .expectedError = "invalidParams", .expectedErrorMessage = "currencyNotString" }, ParamTestCaseBundle{ .testName = "InvalidDirectoryType", .testJson = R"JSON({ "directory": 123 })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "Invalid parameters." }, ParamTestCaseBundle{ .testName = "InvalidDirectoryIndex", .testJson = R"JSON({ "directory": "123" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidDirectoryEmptyJson", .testJson = R"JSON({ "directory": {} })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "missingOwnerOrDirRoot" }, ParamTestCaseBundle{ .testName = "InvalidDirectoryWrongOwnerNotString", .testJson = R"JSON({ "directory": { "owner": 123 } })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "ownerNotString" }, ParamTestCaseBundle{ .testName = "InvalidDirectoryWrongOwnerFormat", .testJson = R"JSON({ "directory": { "owner": "123" } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "InvalidDirectoryWrongDirFormat", .testJson = R"JSON({ "directory": { "dir_root": "123" } })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "dir_rootMalformed" }, ParamTestCaseBundle{ .testName = "InvalidDirectoryWrongDirNotString", .testJson = R"JSON({ "directory": { "dir_root": 123 } })JSON", .expectedError = "invalidParams", .expectedErrorMessage = "dir_rootNotString" }, ParamTestCaseBundle{ .testName = "InvalidDirectoryDirOwnerConflict", .testJson = fmt::format( R"JSON({{ "directory": {{ "dir_root": "{}", "owner": "{}" }} }})JSON", kIndex1, kAccount ), .expectedError = "invalidParams", .expectedErrorMessage = "mayNotSpecifyBothDirRootAndOwner" }, ParamTestCaseBundle{ .testName = "InvalidDirectoryDirSubIndexNotInt", .testJson = fmt::format( R"JSON({{ "directory": {{ "dir_root": "{}", "sub_index": "not int" }} }})JSON", kIndex1 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidAMMStringIndex", .testJson = R"JSON({ "amm": "invalid" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "EmptyAMMJson", .testJson = R"JSON({ "amm": {} })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "NonObjectAMMJsonAsset", .testJson = R"JSON({ "amm": { "asset": 123, "asset2": 123 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "EmptyAMMAssetJson", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset": {{}}, "asset2": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "EmptyAMMAsset2Json", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": {{}}, "asset": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "MissingAMMAsset2Json", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "MissingAMMAssetJson", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "AMMAssetNotJson", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset": "invalid", "asset2": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "AMMAsset2NotJson", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": "invalid", "asset": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "WrongAMMAssetCurrency", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": {{ "currency": "XRP" }}, "asset": {{ "currency": "USD2", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "WrongAMMAssetIssuer", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": {{ "currency": "XRP" }}, "asset": {{ "currency": "USD", "issuer": "aa{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "MissingAMMAssetIssuerForNonXRP", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": {{ "currency": "JPY" }}, "asset": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "AMMAssetHasIssuerForXRP", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": {{ "currency": "XRP", "issuer": "{}" }}, "asset": {{ "currency": "USD", "issuer": "{}" }} }} }})JSON", kAccount, kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "MissingAMMAssetCurrency", .testJson = fmt::format( R"JSON({{ "amm": {{ "asset2": {{ "currency": "XRP" }}, "asset": {{ "issuer": "{}" }} }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeMissingBridgeAccount", .testJson = fmt::format( R"JSON({{ "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeCurrencyIsNumber", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": {}, "issuer": "{}" }} }} }})JSON", kAccount, kAccount, kAccount, 1, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeIssuerIsNumber", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": {} }} }} }})JSON", kAccount, kAccount, kAccount, "JPY", 2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeIssuingChainIssueIsNotObject", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": 1 }} }})JSON", kAccount, kAccount, kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeWithInvalidBridgeAccount", .testJson = fmt::format( R"JSON({{ "bridge_account": "abcd", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeDoorInvalid", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "abcd", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeIssuerInvalid", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "invalid" }} }} }})JSON", kAccount, kAccount, kAccount, "JPY" ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeIssueCurrencyInvalid", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "JPJPJP", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, kAccount2, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeIssueXRPCurrencyInvalid", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP", "issuer": "{}" }}, "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, kAccount2, kAccount2, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeIssueJPYCurrencyInvalid", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "JPY" }} }} }})JSON", kAccount, kAccount, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeMissingLockingChainDoor", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP", "issuer": "{}" }}, "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }} }} }})JSON", kAccount, kAccount2, kAccount2, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeMissingIssuingChainDoor", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeMissingLockingChainIssue", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "IssuingChainDoor": "{}", "LockingChainDoor": "{}", "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, kAccount, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeMissingIssuingChainIssue", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": {{ "IssuingChainDoor": "{}", "LockingChainDoor": "{}", "LockingChainIssue": {{ "currency": "JPY", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, kAccount, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "BridgeInvalidType", .testJson = fmt::format( R"JSON({{ "bridge_account": "{}", "bridge": "invalid" }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedClaimIdInvalidType", .testJson = R"JSON({ "xchain_owned_claim_id": 123 })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedClaimIdJsonMissingClaimId", .testJson = fmt::format( R"JSON({{ "xchain_owned_claim_id": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedClaimIdJsonMissingDoor", .testJson = fmt::format( R"JSON({{ "xchain_owned_claim_id": {{ "xchain_owned_claim_id": 10, "LockingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedClaimIdJsonMissingIssue", .testJson = fmt::format( R"JSON({{ "xchain_owned_claim_id": {{ "xchain_owned_claim_id": 10, "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }} }} }})JSON", kAccount, kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedClaimIdJsonInvalidDoor", .testJson = fmt::format( R"JSON({{ "xchain_owned_claim_id": {{ "xchain_owned_claim_id": 10, "LockingChainDoor": "abcd", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedClaimIdJsonInvalidIssue", .testJson = fmt::format( R"JSON({{ "xchain_owned_claim_id": {{ "xchain_owned_claim_id": 10, "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}" }} }} }})JSON", kAccount, kAccount, "JPY" ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedCreateAccountClaimIdInvalidType", .testJson = R"JSON({ "xchain_owned_create_account_claim_id": 123 })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedCreateAccountClaimIdJsonMissingClaimId", .testJson = fmt::format( R"JSON({{ "xchain_owned_create_account_claim_id": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedCreateAccountClaimIdJsonMissingDoor", .testJson = fmt::format( R"JSON({{ "xchain_owned_create_account_claim_id": {{ "xchain_owned_create_account_claim_id": 10, "LockingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedCreateAccountClaimIdJsonMissingIssue", .testJson = fmt::format( R"JSON({{ "xchain_owned_create_account_claim_id": {{ "xchain_owned_create_account_claim_id": 10, "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }} }} }})JSON", kAccount, kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedCreateAccountClaimIdJsonInvalidDoor", .testJson = fmt::format( R"JSON({{ "xchain_owned_create_account_claim_id": {{ "xchain_owned_create_account_claim_id": 10, "LockingChainDoor": "abcd", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", kAccount, "JPY", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OwnedCreateAccountClaimIdJsonInvalidIssue", .testJson = fmt::format( R"JSON({{ "xchain_owned_create_account_claim_id": {{ "xchain_owned_create_account_claim_id": 10, "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "{}" }} }} }})JSON", kAccount, kAccount, "JPY" ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OracleObjectDocumentIdMissing", .testJson = fmt::format( R"JSON({{ "oracle": {{ "account": "{}" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OracleObjectDocumentIdInvalidNegative", .testJson = fmt::format( R"JSON({{ "oracle": {{ "account": "{}", "oracle_document_id": -1 }} }})JSON", kAccount ), .expectedError = "malformedDocumentID", .expectedErrorMessage = "Malformed oracle_document_id." }, ParamTestCaseBundle{ .testName = "OracleObjectDocumentIdInvalidTypeString", .testJson = fmt::format( R"JSON({{ "oracle": {{ "account": "{}", "oracle_document_id": "invalid" }} }})JSON", kAccount ), .expectedError = "malformedDocumentID", .expectedErrorMessage = "Malformed oracle_document_id." }, ParamTestCaseBundle{ .testName = "OracleObjectDocumentIdInvalidTypeDouble", .testJson = fmt::format( R"JSON({{ "oracle": {{ "account": "{}", "oracle_document_id": 3.21 }} }})JSON", kAccount ), .expectedError = "malformedDocumentID", .expectedErrorMessage = "Malformed oracle_document_id." }, ParamTestCaseBundle{ .testName = "OracleObjectDocumentIdInvalidTypeObject", .testJson = fmt::format( R"JSON({{ "oracle": {{ "account": "{}", "oracle_document_id": {{}} }} }})JSON", kAccount ), .expectedError = "malformedDocumentID", .expectedErrorMessage = "Malformed oracle_document_id." }, ParamTestCaseBundle{ .testName = "OracleObjectDocumentIdInvalidTypeArray", .testJson = fmt::format( R"JSON({{ "oracle": {{ "account": "{}", "oracle_document_id": [] }} }})JSON", kAccount ), .expectedError = "malformedDocumentID", .expectedErrorMessage = "Malformed oracle_document_id." }, ParamTestCaseBundle{ .testName = "OracleObjectDocumentIdInvalidTypeNull", .testJson = fmt::format( R"JSON({{ "oracle": {{ "account": "{}", "oracle_document_id": null }} }})JSON", kAccount ), .expectedError = "malformedDocumentID", .expectedErrorMessage = "Malformed oracle_document_id." }, ParamTestCaseBundle{ .testName = "OracleObjectAccountMissing", .testJson = R"JSON({ "oracle": { "oracle_document_id": 1 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OracleObjectAccountInvalidTypeInteger", .testJson = R"JSON({ "oracle": { "account": 123, "oracle_document_id": 1 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "OracleObjectAccountInvalidTypeDouble", .testJson = R"JSON({ "oracle": { "account": 123.45, "oracle_document_id": 1 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "OracleObjectAccountInvalidTypeNull", .testJson = R"JSON({ "oracle": { "account": null, "oracle_document_id": 1 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "OracleObjectAccountInvalidTypeObject", .testJson = R"JSON({ "oracle": { "account": {"test": "test"}, "oracle_document_id": 1 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "OracleObjectAccountInvalidTypeArray", .testJson = R"JSON({ "oracle": { "account": [{"test": "test"}], "oracle_document_id": 1 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "OracleObjectAccountInvalidFormat", .testJson = R"JSON({ "oracle": { "account": "NotHex", "oracle_document_id": 1 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "OracleStringInvalidFormat", .testJson = R"JSON({ "oracle": "NotHex" })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "OracleStringInvalidTypeInteger", .testJson = R"JSON({ "oracle": 123 })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OracleStringInvalidTypeDouble", .testJson = R"JSON({ "oracle": 123.45 })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OracleStringInvalidTypeArray", .testJson = R"JSON({ "oracle": [{"test": "test"}] })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "OracleStringInvalidTypeNull", .testJson = R"JSON({ "oracle": null })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "CredentialInvalidSubjectType", .testJson = R"JSON({ "credential": { "subject": 123 } })JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "CredentialInvalidIssuerType", .testJson = fmt::format( R"JSON({{ "credential": {{ "issuer": ["{}"] }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidMPTIssuanceStringIndex", .testJson = R"JSON({ "mpt_issuance": "invalid" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidMPTIssuanceType", .testJson = R"JSON({ "mpt_issuance": 0 })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidMPTokenStringIndex", .testJson = R"JSON({ "mptoken": "invalid" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidMPTokenObject", .testJson = fmt::format( R"JSON({{ "mptoken": {{}} }})JSON" ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "MissingMPTokenID", .testJson = fmt::format( R"JSON({{ "mptoken": {{ "account": "{}" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "CredentialInvalidCredentialType", .testJson = fmt::format( R"JSON({{ "credential": {{ "subject": "{}", "issuer": "{}", "credential_type": 1234 }} }})JSON", kAccount, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "CredentialMissingIssuerField", .testJson = fmt::format( R"JSON({{ "credential": {{ "subject": "{}", "credential_type": "1234" }} }})JSON", kAccount, kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidMPTokenAccount", .testJson = fmt::format( R"JSON({{ "mptoken": {{ "mpt_issuance_id": "0000019315EABA24E6135A4B5CE2899E0DA791206413B33D", "account": 1 }} }})JSON" ), .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "InvalidMPTokenType", .testJson = fmt::format( R"JSON({{ "mptoken": 0 }})JSON" ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "InvalidPermissionedDomain_NotObject", .testJson = R"JSON({"permissioned_domain": []})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidPermissionedDomain_InvalidString", .testJson = R"JSON({"permissioned_domain": "invalid_string"})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidPermissionedDomain_EmptyObject", .testJson = R"JSON({"permissioned_domain": {}})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidPermissionedDomain_BadAccount", .testJson = R"JSON({"permissioned_domain": {"account": "1234", "seq": 1234}})JSON", .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address.", }, ParamTestCaseBundle{ .testName = "InvalidPermissionedDomain_MissingSeq", .testJson = fmt::format( R"JSON({{ "permissioned_domain": {{ "account": "{}" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidPermissionedDomain_SeqIsNotUint", .testJson = fmt::format( R"JSON({{ "permissioned_domain": {{ "account": "{}", "seq": -1 }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidPermissionedDomain_BothAccountAndSeqAreInvalid", .testJson = R"JSON({ "permissioned_domain": { "account": "", "seq": -1 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidVault_Type", .testJson = R"JSON({ "vault": 0 })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidVault_NotHex", .testJson = R"JSON({ "vault": "invalid_hex" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "MissingOwner", .testJson = R"JSON({ "vault": { "seq": 1 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "MissingSeq", .testJson = R"JSON({ "vault": { "owner": "abcd" } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "SeqNotInteger", .testJson = R"JSON({ "vault": { "owner": "abcd", "seq": "notAnInteger" } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "InvalidOwnerFormat", .testJson = R"JSON({ "vault": { "owner": "abcd", "seq": 10 } })JSON", .expectedError = "malformedOwner", .expectedErrorMessage = "Malformed owner.", }, ParamTestCaseBundle{ .testName = "BothOwnerAndSeqInvalid", .testJson = R"JSON({ "vault": { "owner": "abcd", "seq": -200 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, // LoanBroker tests ParamTestCaseBundle{ .testName = "LoanBroker_InvalidType", .testJson = R"JSON({"loan_broker": 0})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "LoanBroker_NotHex", .testJson = R"JSON({ "loan_broker": "invalid_hex" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "LoanBroker_MissingOwner", .testJson = R"JSON({ "loan_broker": { "seq": 1 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "LoanBroker_MissingSeq", .testJson = R"JSON({ "loan_broker": { "owner": "abcd" } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "LoanBroker_SeqNotInteger", .testJson = fmt::format( R"JSON({{ "loan_broker": {{ "owner": "{}", "seq": "notAnInteger" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "LoanBroker_InvalidOwnerFormat", .testJson = R"JSON({ "loan_broker": { "owner": "abcd", "seq": 10 } })JSON", .expectedError = "malformedOwner", .expectedErrorMessage = "Malformed owner.", }, ParamTestCaseBundle{ .testName = "LoanBroker_NegativeSeq", .testJson = R"JSON({ "loan_broker": { "owner": "abcd", "seq": -200 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, // Loan tests ParamTestCaseBundle{ .testName = "Loan_InvalidType", .testJson = R"JSON({"loan": 0})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "Loan_NotHex", .testJson = R"JSON({ "loan": "invalid_hex" })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "Loan_MissingLoanBrokerId", .testJson = R"JSON({ "loan": { "loan_seq": 1 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "Loan_MissingLoanSeq", .testJson = fmt::format( R"JSON({{ "loan": {{ "loan_broker_id": "{}" }} }})JSON", kIndex1 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "Loan_LoanSeqNotInteger", .testJson = fmt::format( R"JSON({{ "loan": {{ "loan_broker_id": "{}", "loan_seq": "notAnInteger" }} }})JSON", kIndex1 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "Loan_InvalidLoanBrokerIdFormat", .testJson = R"JSON({ "loan": { "loan_broker_id": "invalid_hex", "loan_seq": 10 } })JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "Loan_NegativeLoanSeq", .testJson = fmt::format( R"JSON({{ "loan": {{ "loan_broker_id": "{}", "loan_seq": -200 }} }})JSON", kIndex1 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request.", }, ParamTestCaseBundle{ .testName = "Delegate_InvalidType", .testJson = R"JSON({"delegate": 123})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "Delegate_InvalidStringIndex", .testJson = R"JSON({"delegate": "invalid_hex_string"})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "Delegate_EmptyObject", .testJson = R"JSON({"delegate": {}})JSON", .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "Delegate_MissingAccount", .testJson = fmt::format( R"JSON({{ "delegate": {{ "authorize": "{}" }} }})JSON", kAccount2 ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "Delegate_AccountNotString", .testJson = fmt::format( R"JSON({{ "delegate": {{ "account": 123, "authorize": "{}" }} }})JSON", kAccount2 ), .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "Delegate_AccountInvalid", .testJson = fmt::format( R"JSON({{ "delegate": {{ "account": "invalid_address", "authorize": "{}" }} }})JSON", kAccount2 ), .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "Delegate_MissingAuthorize", .testJson = fmt::format( R"JSON({{ "delegate": {{ "account": "{}" }} }})JSON", kAccount ), .expectedError = "malformedRequest", .expectedErrorMessage = "Malformed request." }, ParamTestCaseBundle{ .testName = "Delegate_AuthorizeNotString", .testJson = fmt::format( R"JSON({{ "delegate": {{ "account": "{}", "authorize": 123 }} }})JSON", kAccount ), .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, ParamTestCaseBundle{ .testName = "Delegate_AuthorizeInvalid", .testJson = fmt::format( R"JSON({{ "delegate": {{ "account": "{}", "authorize": "invalid_address" }} }})JSON", kAccount ), .expectedError = "malformedAddress", .expectedErrorMessage = "Malformed address." }, }; } INSTANTIATE_TEST_CASE_P( RPCLedgerEntryGroup1, LedgerEntryParameterTest, ValuesIn(generateTestValuesForParametersTest()), tests::util::kNameGenerator ); TEST_P(LedgerEntryParameterTest, InvalidParams) { auto const testBundle = GetParam(); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse(testBundle.testJson); auto const output = handler.process(req, Context{yield}); 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); }); } // parameterized test cases for index struct IndexTest : public HandlerBaseTest, public WithParamInterface { struct NameGenerator { template std::string operator()(testing::TestParamInfo const& info) const { return static_cast(info.param); } }; }; // content of index, amendments, check, fee, hashes, nft_offer, nunl, nft_page, payment_channel, // signer_list fields is ledger index. INSTANTIATE_TEST_CASE_P( RPCLedgerEntryGroup3, IndexTest, Values( "index", "amendments", "check", "fee", "hashes", "nft_offer", "nunl", "nft_page", "payment_channel", "signer_list" ), IndexTest::NameGenerator{} ); TEST_P(IndexTest, InvalidIndexUint256) { auto const index = GetParam(); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "{}": "invalid" }})JSON", index ) ); auto const output = handler.process(req, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "malformedRequest"); EXPECT_EQ(err.at("error_message").as_string(), "Malformed request."); }); } TEST_P(IndexTest, InvalidIndexNotString) { auto const index = GetParam(); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "{}": 123 }})JSON", index ) ); auto const output = handler.process(req, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "malformedRequest"); EXPECT_EQ(err.at("error_message").as_string(), "Malformed request."); }); } TEST_F(RPCLedgerEntryTest, LedgerEntryNotFound) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); // return null for ledger entry auto const key = ripple::keylet::account(getAccountIdWithString(kAccount)).key; EXPECT_CALL(*backend_, doFetchLedgerObject(key, kRangeMax, _)) .WillRepeatedly(Return(std::optional{})); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "account_root": "{}" }})JSON", kAccount ) ); auto const output = handler.process(req, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "entryNotFound"); }); } struct NormalPathTestBundle { std::string testName; std::string testJson; ripple::uint256 expectedIndex; ripple::STObject mockedEntity; }; struct RPCLedgerEntryNormalPathTest : public RPCLedgerEntryTest, public WithParamInterface {}; static auto generateTestValuesForNormalPathTest() { auto account1 = getAccountIdWithString(kAccount); auto account2 = getAccountIdWithString(kAccount2); ripple::Currency currency; ripple::to_currency(currency, "USD"); return std::vector{ NormalPathTestBundle{ .testName = "Index", .testJson = fmt::format( R"JSON({{ "binary": true, "index": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createAccountRootObject(kAccount2, ripple::lsfGlobalFreeze, 1, 10, 2, kIndex1, 3) }, NormalPathTestBundle{ .testName = "Payment_channel", .testJson = fmt::format( R"JSON({{ "binary": true, "payment_channel": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createPaymentChannelLedgerObject(kAccount, kAccount2, 100, 200, 300, kIndex1, 400) }, NormalPathTestBundle{ .testName = "Nft_page", .testJson = fmt::format( R"JSON({{ "binary": true, "nft_page": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createNftTokenPage( std::vector{std::make_pair(kTokenId, "www.ok.com")}, std::nullopt ) }, NormalPathTestBundle{ .testName = "Check", .testJson = fmt::format( R"JSON({{ "binary": true, "check": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createCheckLedgerObject(kAccount, kAccount2) }, NormalPathTestBundle{ .testName = "DirectoryIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "directory": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createOwnerDirLedgerObject( std::vector{ripple::uint256{kIndex1}}, kIndex1 ) }, NormalPathTestBundle{ .testName = "OfferIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "offer": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createOfferLedgerObject( kAccount, 100, 200, "USD", "XRP", kAccount2, ripple::toBase58(ripple::xrpAccount()), kIndex1 ) }, NormalPathTestBundle{ .testName = "EscrowIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "escrow": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createEscrowLedgerObject(kAccount, kAccount2) }, NormalPathTestBundle{ .testName = "TicketIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "ticket": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createTicketLedgerObject(kAccount, 0) }, NormalPathTestBundle{ .testName = "DepositPreauthIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "deposit_preauth": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createDepositPreauthLedgerObjectByAuth(kAccount, kAccount2) }, NormalPathTestBundle{ .testName = "AccountRoot", .testJson = fmt::format( R"JSON({{ "binary": true, "account_root": "{}" }})JSON", kAccount ), .expectedIndex = ripple::keylet::account(getAccountIdWithString(kAccount)).key, .mockedEntity = createAccountRootObject(kAccount, 0, 1, 1, 1, kIndex1, 1) }, NormalPathTestBundle{ .testName = "DID", .testJson = fmt::format( R"JSON({{ "binary": true, "did": "{}" }})JSON", kAccount ), .expectedIndex = ripple::keylet::did(getAccountIdWithString(kAccount)).key, .mockedEntity = createDidObject(kAccount, "mydocument", "myURI", "mydata") }, NormalPathTestBundle{ .testName = "DirectoryViaDirRoot", .testJson = fmt::format( R"JSON({{ "binary": true, "directory": {{ "dir_root": "{}", "sub_index": 2 }} }})JSON", kIndex1 ), .expectedIndex = ripple::keylet::page(ripple::uint256{kIndex1}, 2).key, .mockedEntity = createOwnerDirLedgerObject( std::vector{ripple::uint256{kIndex1}}, kIndex1 ) }, NormalPathTestBundle{ .testName = "DirectoryViaOwner", .testJson = fmt::format( R"JSON({{ "binary": true, "directory": {{ "owner": "{}", "sub_index": 2 }} }})JSON", kAccount ), .expectedIndex = ripple::keylet::page(ripple::keylet::ownerDir(account1), 2).key, .mockedEntity = createOwnerDirLedgerObject( std::vector{ripple::uint256{kIndex1}}, kIndex1 ) }, NormalPathTestBundle{ .testName = "DirectoryViaDefaultSubIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "directory": {{ "owner": "{}" }} }})JSON", kAccount ), // default sub_index is 0 .expectedIndex = ripple::keylet::page(ripple::keylet::ownerDir(account1), 0).key, .mockedEntity = createOwnerDirLedgerObject( std::vector{ripple::uint256{kIndex1}}, kIndex1 ) }, NormalPathTestBundle{ .testName = "Escrow", .testJson = fmt::format( R"JSON({{ "binary": true, "escrow": {{ "owner": "{}", "seq": 1 }} }})JSON", kAccount ), .expectedIndex = ripple::keylet::escrow(account1, 1).key, .mockedEntity = createEscrowLedgerObject(kAccount, kAccount2) }, NormalPathTestBundle{ .testName = "DepositPreauthByAuth", .testJson = fmt::format( R"JSON({{ "binary": true, "deposit_preauth": {{ "owner": "{}", "authorized": "{}" }} }})JSON", kAccount, kAccount2 ), .expectedIndex = ripple::keylet::depositPreauth(account1, account2).key, .mockedEntity = createDepositPreauthLedgerObjectByAuth(kAccount, kAccount2) }, NormalPathTestBundle{ .testName = "DepositPreauthByAuthCredentials", .testJson = fmt::format( R"JSON({{ "binary": true, "deposit_preauth": {{ "owner": "{}", "authorized_credentials": [ {{ "issuer": "{}", "credential_type": "{}" }} ] }} }})JSON", kAccount, kAccount2, kCredentialType ), .expectedIndex = ripple::keylet::depositPreauth( account1, credentials::createAuthCredentials(createAuthCredentialArray( std::vector{kAccount2}, std::vector{kCredentialType} )) ) .key, .mockedEntity = createDepositPreauthLedgerObjectByAuthCredentials( kAccount, kAccount2, kCredentialType ) }, NormalPathTestBundle{ .testName = "Credentials", .testJson = fmt::format( R"JSON({{ "binary": true, "credential": {{ "subject": "{}", "issuer": "{}", "credential_type": "{}" }} }})JSON", kAccount, kAccount2, kCredentialType ), .expectedIndex = ripple::keylet::credential( account1, account2, ripple::Slice( // NOLINTNEXTLINE(bugprone-unchecked-optional-access) ripple::strUnHex(kCredentialType)->data(), // NOLINTNEXTLINE(bugprone-unchecked-optional-access) ripple::strUnHex(kCredentialType)->size() ) ) .key, .mockedEntity = createCredentialObject(kAccount, kAccount2, kCredentialType) }, NormalPathTestBundle{ .testName = "RippleState", .testJson = fmt::format( R"JSON({{ "binary": true, "ripple_state": {{ "accounts": ["{}", "{}"], "currency": "USD" }} }})JSON", kAccount, kAccount2 ), .expectedIndex = ripple::keylet::line(account1, account2, currency).key, .mockedEntity = createRippleStateLedgerObject( "USD", kAccount2, 100, kAccount, 10, kAccount2, 20, kIndex1, 123, 0 ) }, NormalPathTestBundle{ .testName = "Ticket", .testJson = fmt::format( R"JSON({{ "binary": true, "ticket": {{ "account": "{}", "ticket_seq": 2 }} }})JSON", kAccount ), .expectedIndex = ripple::getTicketIndex(account1, 2), .mockedEntity = createTicketLedgerObject(kAccount, 0) }, NormalPathTestBundle{ .testName = "Offer", .testJson = fmt::format( R"JSON({{ "binary": true, "offer": {{ "account": "{}", "seq": 2 }} }})JSON", kAccount ), .expectedIndex = ripple::keylet::offer(account1, 2).key, .mockedEntity = createOfferLedgerObject( kAccount, 100, 200, "USD", "XRP", kAccount2, ripple::toBase58(ripple::xrpAccount()), kIndex1 ) }, NormalPathTestBundle{ .testName = "AMMViaIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "amm": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createAmmObject( kAccount, "XRP", ripple::toBase58(ripple::xrpAccount()), "JPY", kAccount2 ) }, NormalPathTestBundle{ .testName = "AMMViaJson", .testJson = fmt::format( R"JSON({{ "binary": true, "amm": {{ "asset": {{ "currency": "XRP" }}, "asset2": {{ "currency": "{}", "issuer": "{}" }} }} }})JSON", "JPY", kAccount2 ), .expectedIndex = ripple::keylet::amm( getIssue("XRP", ripple::toBase58(ripple::xrpAccount())), getIssue("JPY", kAccount2) ) .key, .mockedEntity = createAmmObject( kAccount, "XRP", ripple::toBase58(ripple::xrpAccount()), "JPY", kAccount2 ) }, NormalPathTestBundle{ .testName = "BridgeLocking", .testJson = fmt::format( R"JSON({{ "binary": true, "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }} }} }})JSON", kAccount, kAccount, kAccount2, kAccount3 ), .expectedIndex = ripple::keylet::bridge( ripple::STXChainBridge( getAccountIdWithString(kAccount), ripple::xrpIssue(), getAccountIdWithString(kAccount2), getIssue("JPY", kAccount3) ), ripple::STXChainBridge::ChainType::locking ) .key, .mockedEntity = createBridgeObject(kAccount, kAccount, kAccount2, "JPY", kAccount3) }, NormalPathTestBundle{ .testName = "BridgeIssuing", .testJson = fmt::format( R"JSON({{ "binary": true, "bridge_account": "{}", "bridge": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }} }} }})JSON", kAccount2, kAccount, kAccount2, kAccount3 ), .expectedIndex = ripple::keylet::bridge( ripple::STXChainBridge( getAccountIdWithString(kAccount), ripple::xrpIssue(), getAccountIdWithString(kAccount2), getIssue("JPY", kAccount3) ), ripple::STXChainBridge::ChainType::issuing ) .key, .mockedEntity = createBridgeObject(kAccount, kAccount, kAccount2, "JPY", kAccount3) }, NormalPathTestBundle{ .testName = "XChainOwnedClaimId", .testJson = fmt::format( R"JSON({{ "binary": true, "xchain_owned_claim_id": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }}, "xchain_owned_claim_id": 10 }} }})JSON", kAccount, kAccount2, kAccount3 ), .expectedIndex = ripple::keylet::xChainClaimID( ripple::STXChainBridge( getAccountIdWithString(kAccount), ripple::xrpIssue(), getAccountIdWithString(kAccount2), getIssue("JPY", kAccount3) ), 10 ) .key, .mockedEntity = createChainOwnedClaimIdObject( kAccount, kAccount, kAccount2, "JPY", kAccount3, kAccount ) }, NormalPathTestBundle{ .testName = "XChainOwnedCreateAccountClaimId", .testJson = fmt::format( R"JSON({{ "binary": true, "xchain_owned_create_account_claim_id": {{ "LockingChainDoor": "{}", "IssuingChainDoor": "{}", "LockingChainIssue": {{ "currency": "XRP" }}, "IssuingChainIssue": {{ "currency": "JPY", "issuer": "{}" }}, "xchain_owned_create_account_claim_id": 10 }} }})JSON", kAccount, kAccount2, kAccount3 ), .expectedIndex = ripple::keylet::xChainCreateAccountClaimID( ripple::STXChainBridge( getAccountIdWithString(kAccount), ripple::xrpIssue(), getAccountIdWithString(kAccount2), getIssue("JPY", kAccount3) ), 10 ) .key, .mockedEntity = createChainOwnedClaimIdObject( kAccount, kAccount, kAccount2, "JPY", kAccount3, kAccount ) }, NormalPathTestBundle{ .testName = "OracleEntryFoundViaIntOracleDocumentId", .testJson = fmt::format( R"JSON({{ "binary": true, "oracle": {{ "account": "{}", "oracle_document_id": 1 }} }})JSON", kAccount ), .expectedIndex = ripple::keylet::oracle(getAccountIdWithString(kAccount), 1).key, .mockedEntity = createOracleObject( kAccount, "70726F7669646572", 32u, 1234u, ripple::Blob(8, 's'), ripple::Blob(8, 's'), kRangeMax - 2, ripple::uint256{"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321"}, createPriceDataSeries({createOraclePriceData( 2e4, ripple::to_currency("XRP"), ripple::to_currency("USD"), 3 )}) ) }, NormalPathTestBundle{ .testName = "OracleEntryFoundViaStrOracleDocumentId", .testJson = fmt::format( R"JSON({{ "binary": true, "oracle": {{ "account": "{}", "oracle_document_id": "1" }} }})JSON", kAccount ), .expectedIndex = ripple::keylet::oracle(getAccountIdWithString(kAccount), 1).key, .mockedEntity = createOracleObject( kAccount, "70726F7669646572", 32u, 1234u, ripple::Blob(8, 's'), ripple::Blob(8, 's'), kRangeMax - 2, ripple::uint256{"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321"}, createPriceDataSeries({createOraclePriceData( 2e4, ripple::to_currency("XRP"), ripple::to_currency("USD"), 3 )}) ) }, NormalPathTestBundle{ .testName = "OracleEntryFoundViaString", .testJson = fmt::format( R"JSON({{ "binary": true, "oracle": "{}" }})JSON", ripple::to_string(ripple::keylet::oracle(getAccountIdWithString(kAccount), 1).key) ), .expectedIndex = ripple::keylet::oracle(getAccountIdWithString(kAccount), 1).key, .mockedEntity = createOracleObject( kAccount, "70726F7669646572", 64u, 4321u, ripple::Blob(8, 'a'), ripple::Blob(8, 'a'), kRangeMax - 4, ripple::uint256{"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321"}, createPriceDataSeries({createOraclePriceData( 1e3, ripple::to_currency("USD"), ripple::to_currency("XRP"), 2 )}) ) }, NormalPathTestBundle{ .testName = "MPTIssuance", .testJson = fmt::format( R"JSON({{ "binary": true, "mpt_issuance": "{}" }})JSON", ripple::to_string(ripple::makeMptID(2, account1)) ), .expectedIndex = ripple::keylet::mptIssuance(ripple::makeMptID(2, account1)).key, .mockedEntity = createMptIssuanceObject(kAccount, 2, "metadata") }, NormalPathTestBundle{ .testName = "MPTokenViaIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "mptoken": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createMpTokenObject(kAccount, ripple::makeMptID(2, account1)) }, NormalPathTestBundle{ .testName = "MPTokenViaObject", .testJson = fmt::format( R"JSON({{ "binary": true, "mptoken": {{ "account": "{}", "mpt_issuance_id": "{}" }} }})JSON", kAccount, ripple::to_string(ripple::makeMptID(2, account1)) ), .expectedIndex = ripple::keylet::mptoken(ripple::makeMptID(2, account1), account1).key, .mockedEntity = createMpTokenObject(kAccount, ripple::makeMptID(2, account1)) }, NormalPathTestBundle{ .testName = "PermissionedDomainViaString", .testJson = fmt::format( R"JSON({{ "binary": true, "permissioned_domain": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256(kIndex1), .mockedEntity = createPermissionedDomainObject( kAccount, kIndex1, kRangeMax, 0, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "PermissionedDomainViaObject", .testJson = fmt::format( R"JSON({{ "binary": true, "permissioned_domain": {{ "account": "{}", "seq": {} }} }})JSON", kAccount, kRangeMax ), .expectedIndex = ripple::keylet::permissionedDomain( // NOLINTNEXTLINE(bugprone-unchecked-optional-access) *ripple::parseBase58(kAccount), kRangeMax ) .key, .mockedEntity = createPermissionedDomainObject( kAccount, kIndex1, kRangeMax, 0, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "CreateVaultObjectByHexString", .testJson = fmt::format( R"JSON({{ "binary": true, "vault": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256(kIndex1), .mockedEntity = createVault( kAccount, kAccount, kRangeMax, "XRP", ripple::toBase58(ripple::xrpAccount()), ripple::uint192(0), 0, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "CreateVaultObjectByAccount", .testJson = fmt::format( R"JSON({{ "binary": true, "vault": {{ "owner": "{}", "seq": {} }} }})JSON", kAccount, kRangeMax ), .expectedIndex = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) ripple::keylet::vault(*ripple::parseBase58(kAccount), kRangeMax).key, .mockedEntity = createVault( kAccount, kAccount, kRangeMax, "XRP", ripple::toBase58(ripple::xrpAccount()), ripple::uint192(0), 0, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "CreateLoanBrokerObjectByHexString", .testJson = fmt::format( R"JSON({{ "binary": true, "loan_broker": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256(kIndex1), .mockedEntity = createLoanBroker( kAccount, kAccount, kRangeMax, ripple::uint256{kIndex1}, 1, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "CreateLoanBrokerObjectByOwnerAndSeq", .testJson = fmt::format( R"JSON({{ "binary": true, "loan_broker": {{ "owner": "{}", "seq": {} }} }})JSON", kAccount, kRangeMax ), .expectedIndex = ripple::keylet::loanbroker( // NOLINTNEXTLINE(bugprone-unchecked-optional-access) *ripple::parseBase58(kAccount), kRangeMax ) .key, .mockedEntity = createLoanBroker( kAccount, kAccount, kRangeMax, ripple::uint256{kIndex1}, 1, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "CreateLoanObjectByHexString", .testJson = fmt::format( R"JSON({{ "binary": true, "loan": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256(kIndex1), .mockedEntity = createLoan( kAccount, ripple::uint256{kIndex1}, 1, 1000, 86400, 100, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "CreateLoanObjectByLoanBrokerIdAndSeq", .testJson = fmt::format( R"JSON({{ "binary": true, "loan": {{ "loan_broker_id": "{}", "loan_seq": 1 }} }})JSON", kIndex1 ), .expectedIndex = ripple::keylet::loan(ripple::uint256{kIndex1}, 1).key, .mockedEntity = createLoan( kAccount, ripple::uint256{kIndex1}, 1, 1000, 86400, 100, ripple::uint256{0}, 0 ) }, NormalPathTestBundle{ .testName = "DelegateViaStringIndex", .testJson = fmt::format( R"JSON({{ "binary": true, "delegate": "{}" }})JSON", kIndex1 ), .expectedIndex = ripple::uint256{kIndex1}, .mockedEntity = createDelegateObject(kAccount, kAccount2, kIndex1, 0, ripple::uint256{0}, 0) }, NormalPathTestBundle{ .testName = "DelegateViaObject", .testJson = fmt::format( R"JSON({{ "binary": true, "delegate": {{ "account": "{}", "authorize": "{}" }} }})JSON", kAccount, kAccount2 ), .expectedIndex = ripple::keylet::delegate( getAccountIdWithString(kAccount), getAccountIdWithString(kAccount2) ) .key, .mockedEntity = createDelegateObject(kAccount, kAccount2, kIndex1, 0, ripple::uint256{0}, 0) }, }; } INSTANTIATE_TEST_CASE_P( RPCLedgerEntryGroup2, RPCLedgerEntryNormalPathTest, ValuesIn(generateTestValuesForNormalPathTest()), tests::util::kNameGenerator ); // Test for normal path // Check the index in response matches the computed index accordingly TEST_P(RPCLedgerEntryNormalPathTest, NormalPath) { auto const testBundle = GetParam(); auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); EXPECT_CALL(*backend_, doFetchLedgerObject(testBundle.expectedIndex, kRangeMax, _)) .WillRepeatedly(Return(testBundle.mockedEntity.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse(testBundle.testJson); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); auto const& outputJson = output.result.value(); EXPECT_EQ(outputJson.at("ledger_hash").as_string(), kLedgerHash); EXPECT_EQ(outputJson.at("ledger_index").as_uint64(), kRangeMax); EXPECT_EQ( outputJson.at("node_binary").as_string(), ripple::strHex(testBundle.mockedEntity.getSerializer().peekData()) ); EXPECT_EQ( ripple::uint256(boost::json::value_to(outputJson.at("index")).data()), testBundle.expectedIndex ); }); } // this testcase will test the deserialization of ledger entry TEST_F(RPCLedgerEntryTest, BinaryFalse) { static constexpr auto kOut = R"JSON({ "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "node": { "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Amount": "100", "Balance": "200", "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "Flags": 0, "LedgerEntryType": "PayChannel", "OwnerNode": "0", "PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "PreviousTxnLgrSeq": 400, "PublicKey": "020000000000000000000000000000000000000000000000000000000000000000", "SettleDelay": 300, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" } })JSON"; auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); // return valid ledger entry which can be deserialized auto const ledgerEntry = createPaymentChannelLedgerObject(kAccount, kAccount2, 100, 200, 300, kIndex1, 400); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillRepeatedly(Return(ledgerEntry.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "payment_channel": "{}" }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(kOut)); }); } TEST_F(RPCLedgerEntryTest, Vault_BinaryFalse) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); boost::json::object const entry; auto const vault = createVault( kAccount, kAccount, kRangeMax, "XRP", ripple::toBase58(ripple::xrpAccount()), ripple::uint192(0), 0, ripple::uint256{1}, 0 ); auto const vaultKey = // NOLINTNEXTLINE(bugprone-unchecked-optional-access) ripple::keylet::vault(*ripple::parseBase58(kAccount), kRangeMax).key; ripple::STLedgerEntry const sle{ ripple::SerialIter{ vault.getSerializer().peekData().data(), vault.getSerializer().peekData().size() }, vaultKey }; EXPECT_CALL(*backend_, doFetchLedgerObject(vaultKey, testing::_, testing::_)) .WillOnce(Return(vault.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "binary": false, "vault": {{ "owner": "{}", "seq": {} }} }})JSON", kAccount, kRangeMax ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(output.result->at("node").at("Owner").as_string(), kAccount); EXPECT_EQ(output.result->at("node").at("Sequence").as_int64(), kRangeMax); }); } TEST_F(RPCLedgerEntryTest, LoanBroker_BinaryFalse) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); boost::json::object const entry; auto const loanBroker = createLoanBroker( kAccount, kAccount, kRangeMax, ripple::uint256{kIndex1}, 1, ripple::uint256{1}, 0 ); auto const loanBrokerKey = ripple::keylet::loanbroker( // NOLINTNEXTLINE(bugprone-unchecked-optional-access) *ripple::parseBase58(kAccount), kRangeMax ) .key; ripple::STLedgerEntry const sle{ ripple::SerialIter{ loanBroker.getSerializer().peekData().data(), loanBroker.getSerializer().peekData().size() }, loanBrokerKey }; EXPECT_CALL(*backend_, doFetchLedgerObject(loanBrokerKey, testing::_, testing::_)) .WillOnce(Return(loanBroker.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "binary": false, "loan_broker": {{ "owner": "{}", "seq": {} }} }})JSON", kAccount, kRangeMax ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(output.result->at("node").at("Owner").as_string(), kAccount); EXPECT_EQ(output.result->at("node").at("Sequence").as_int64(), kRangeMax); EXPECT_EQ(output.result->at("node").at("LoanSequence").as_int64(), 1); }); } TEST_F(RPCLedgerEntryTest, Loan_BinaryFalse) { static constexpr auto kLoanSeq = 1; static constexpr auto kStartDate = 1000; static constexpr auto kPaymentInterval = 86400; static constexpr auto kInterestRate = 100; auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); boost::json::object const entry; auto const loan = createLoan( kAccount, ripple::uint256{kIndex1}, kLoanSeq, kStartDate, kPaymentInterval, kInterestRate, ripple::uint256{1}, 0 ); EXPECT_CALL(*backend_, doFetchLedgerObject(testing::_, testing::_, testing::_)) .WillOnce(Return(loan.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "binary": false, "loan": {{ "loan_broker_id": "{}", "loan_seq": {} }} }})JSON", kIndex1, kLoanSeq ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(output.result->at("node").at("Borrower").as_string(), kAccount); EXPECT_EQ(output.result->at("node").at("LoanSequence").as_int64(), kLoanSeq); EXPECT_EQ(output.result->at("node").at("StartDate").as_int64(), kStartDate); EXPECT_EQ(output.result->at("node").at("PaymentInterval").as_int64(), kPaymentInterval); }); } TEST_F(RPCLedgerEntryTest, UnexpectedLedgerType) { auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); // return valid ledger entry which can be deserialized auto const ledgerEntry = createPaymentChannelLedgerObject(kAccount, kAccount2, 100, 200, 300, kIndex1, 400); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillRepeatedly(Return(ledgerEntry.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "check": "{}" }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); EXPECT_EQ(err.at("error").as_string(), "unexpectedLedgerType"); }); } TEST_F(RPCLedgerEntryTest, LedgerNotExistViaIntSequence) { EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(std::nullopt)); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "check": "{}", "ledger_index": {} }})JSON", kIndex1, kRangeMax ) ); auto const output = handler.process(req, 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(RPCLedgerEntryTest, LedgerNotExistViaStringSequence) { EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(std::nullopt)); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "check": "{}", "ledger_index": "{}" }})JSON", kIndex1, kRangeMax ) ); auto const output = handler.process(req, 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(RPCLedgerEntryTest, LedgerNotExistViaHash) { EXPECT_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLedgerHash}, _)) .WillRepeatedly(Return(std::nullopt)); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "check": "{}", "ledger_hash": "{}" }})JSON", kIndex1, kLedgerHash ) ); auto const output = handler.process(req, 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(RPCLedgerEntryTest, InvalidEntryTypeVersion2) { runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse(R"JSON({})JSON"); 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(), "invalidParams"); EXPECT_EQ(err.at("error_message").as_string(), "No ledger_entry params provided."); }); } TEST_F(RPCLedgerEntryTest, InvalidEntryTypeVersion1) { runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse(R"JSON({})JSON"); 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(), "unknownOption"); EXPECT_EQ(err.at("error_message").as_string(), "Unknown option."); }); } TEST(RPCLedgerEntrySpecTest, DeprecatedFields) { boost::json::value const json{{"ledger", 2}}; auto const spec = LedgerEntryHandler::spec(2); auto const warnings = spec.check(json); ASSERT_EQ(warnings.size(), 1); ASSERT_TRUE(warnings[0].is_object()); auto const& warning = warnings[0].as_object(); ASSERT_TRUE(warning.contains("id")); ASSERT_TRUE(warning.contains("message")); EXPECT_EQ( warning.at("id").as_int64(), static_cast(rpc::WarningCode::WarnRpcDeprecated) ); EXPECT_NE( warning.at("message").as_string().find("Field 'ledger' is deprecated."), std::string::npos ) << warning; } // Same as BinaryFalse with include_deleted set to true // Expected Result: same as BinaryFalse TEST_F(RPCLedgerEntryTest, BinaryFalseIncludeDeleted) { static constexpr auto kOut = R"JSON({ "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "node": { "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Amount": "100", "Balance": "200", "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "Flags": 0, "LedgerEntryType": "PayChannel", "OwnerNode": "0", "PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "PreviousTxnLgrSeq": 400, "PublicKey": "020000000000000000000000000000000000000000000000000000000000000000", "SettleDelay": 300, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" } })JSON"; // return valid ledgerinfo auto const ledgerinfo = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)).WillRepeatedly(Return(ledgerinfo)); // return valid ledger entry which can be deserialized auto const ledgerEntry = createPaymentChannelLedgerObject(kAccount, kAccount2, 100, 200, 300, kIndex1, 400); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillRepeatedly(Return(ledgerEntry.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "index": "{}", "include_deleted": true }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(kOut)); }); } // Test for object is deleted in the latest sequence // Expected Result: return the latest object that is not deleted TEST_F(RPCLedgerEntryTest, LedgerEntryDeleted) { static constexpr auto kOut = R"JSON({ "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "deleted_ledger_index": 30, "node": { "Amount": "123", "Flags": 0, "LedgerEntryType": "NFTokenOffer", "NFTokenID": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "NFTokenOfferNode": "0", "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "OwnerNode": "0", "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", "PreviousTxnLgrSeq": 0, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" } })JSON"; auto const ledgerinfo = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)).WillRepeatedly(Return(ledgerinfo)); // return valid ledger entry which can be deserialized auto const offer = createNftBuyOffer(kNftId, kAccount); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObjectSeq(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(uint32_t{kRangeMax})); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax - 1, _)) .WillOnce(Return(offer.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "index": "{}", "include_deleted": true }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(kOut)); }); } // Test for object not exist in database // Expected Result: return entryNotFound error TEST_F(RPCLedgerEntryTest, LedgerEntryNotExist) { auto const ledgerinfo = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)).WillRepeatedly(Return(ledgerinfo)); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObjectSeq(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(uint32_t{kRangeMax})); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax - 1, _)) .WillOnce(Return(std::optional{})); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "index": "{}", "include_deleted": true }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); auto const myerr = err.at("error").as_string(); EXPECT_EQ(myerr, "entryNotFound"); }); } // Same as BinaryFalse with include_deleted set to false // Expected Result: same as BinaryFalse TEST_F(RPCLedgerEntryTest, BinaryFalseIncludeDeleteFalse) { static constexpr auto kOut = R"JSON({ "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "node": { "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "Amount": "100", "Balance": "200", "Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "Flags": 0, "LedgerEntryType": "PayChannel", "OwnerNode": "0", "PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "PreviousTxnLgrSeq": 400, "PublicKey": "020000000000000000000000000000000000000000000000000000000000000000", "SettleDelay": 300, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" } })JSON"; // return valid ledgerinfo auto const ledgerinfo = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)).WillRepeatedly(Return(ledgerinfo)); // return valid ledger entry which can be deserialized auto const ledgerEntry = createPaymentChannelLedgerObject(kAccount, kAccount2, 100, 200, 300, kIndex1, 400); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillRepeatedly(Return(ledgerEntry.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "payment_channel": "{}", "include_deleted": false }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(kOut)); }); } // Test when an object is updated and include_deleted is set to true // Expected Result: return the latest object that is not deleted (latest object in this test) TEST_F(RPCLedgerEntryTest, ObjectUpdateIncludeDelete) { static constexpr auto kOut = R"JSON({ "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "node": { "Balance": { "currency": "USD", "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "value": "10" }, "Flags": 0, "HighLimit": { "currency": "USD", "issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", "value": "200" }, "LedgerEntryType": "RippleState", "LowLimit": { "currency": "USD", "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "value": "100" }, "PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "PreviousTxnLgrSeq": 123, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" } })JSON"; // return valid ledgerinfo auto const ledgerinfo = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)).WillRepeatedly(Return(ledgerinfo)); // return valid ledger entry which can be deserialized auto const line1 = createRippleStateLedgerObject( "USD", kAccount2, 10, kAccount, 100, kAccount2, 200, kTxnId, 123 ); auto const line2 = createRippleStateLedgerObject( "USD", kAccount, 10, kAccount2, 100, kAccount, 200, kTxnId, 123 ); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillRepeatedly(Return(line1.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax - 1, _)) .WillRepeatedly(Return(line2.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "index": "{}", "include_deleted": true }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(kOut)); }); } // Test when an object is deleted several sequence ago and include_deleted is set to true // Expected Result: return the latest object that is not deleted TEST_F(RPCLedgerEntryTest, ObjectDeletedPreviously) { static constexpr auto kOut = R"JSON({ "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD", "deleted_ledger_index": 26, "node": { "Amount": "123", "Flags": 0, "LedgerEntryType": "NFTokenOffer", "NFTokenID": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "NFTokenOfferNode": "0", "Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "OwnerNode": "0", "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", "PreviousTxnLgrSeq": 0, "index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD" } })JSON"; auto const ledgerinfo = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)).WillRepeatedly(Return(ledgerinfo)); // return valid ledger entry which can be deserialized auto const offer = createNftBuyOffer(kNftId, kAccount); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObjectSeq(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(uint32_t{kRangeMax - 4})); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax - 5, _)) .WillOnce(Return(offer.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "index": "{}", "include_deleted": true }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(kOut)); }); } // Test for object seq not exist in database // Expected Result: return entryNotFound error TEST_F(RPCLedgerEntryTest, ObjectSeqNotExist) { auto const ledgerinfo = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)).WillRepeatedly(Return(ledgerinfo)); EXPECT_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(std::optional{})); EXPECT_CALL(*backend_, doFetchLedgerObjectSeq(ripple::uint256{kIndex1}, kRangeMax, _)) .WillOnce(Return(std::nullopt)); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "index": "{}", "include_deleted": true }})JSON", kIndex1 ) ); auto const output = handler.process(req, Context{yield}); ASSERT_FALSE(output); auto const err = rpc::makeError(output.result.error()); auto const myerr = err.at("error").as_string(); EXPECT_EQ(myerr, "entryNotFound"); }); } // this testcase will test the if response includes synthetic mpt_issuance_id TEST_F(RPCLedgerEntryTest, SyntheticMPTIssuanceID) { static constexpr auto kOut = R"JSON({ "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", "ledger_index": 30, "validated": true, "index": "FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", "node": { "Flags": 0, "Issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "LedgerEntryType": "MPTokenIssuance", "MPTokenMetadata": "6D65746164617461", "OutstandingAmount": "0", "OwnerNode": "0", "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", "PreviousTxnLgrSeq": 0, "Sequence": 2, "index": "FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336", "mpt_issuance_id": "000000024B4E9C06F24296074F7BC48F92A97916C6DC5EA9" } })JSON"; auto const mptId = ripple::makeMptID(2, getAccountIdWithString(kAccount)); auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax); EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) .WillRepeatedly(Return(ledgerHeader)); // return valid ledger entry which can be deserialized auto const ledgerEntry = createMptIssuanceObject(kAccount, 2, "metadata"); EXPECT_CALL( *backend_, doFetchLedgerObject(ripple::keylet::mptIssuance(mptId).key, kRangeMax, _) ) .WillRepeatedly(Return(ledgerEntry.getSerializer().peekData())); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerEntryHandler{backend_}}; auto const req = json::parse( fmt::format( R"JSON({{ "mpt_issuance": "{}" }})JSON", ripple::to_string(mptId) ) ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_EQ(*output.result, json::parse(kOut)); }); }