Compare commits

...

3 Commits

Author SHA1 Message Date
Alex Kremer
9cd44a06dd fix: Internal error in account_info with pseudo account (#3196) 2026-08-28 16:32:50 +01:00
Alex Kremer
e5f11df558 fix: Rare race in cluster tests (#3194) 2026-08-28 16:32:29 +01:00
Ayaz Salikhov
58d8f12ba3 test: Use credentialTypeValidator in LedgerEntry (#3195) 2026-08-27 16:40:19 +01:00
4 changed files with 117 additions and 11 deletions

View File

@@ -182,7 +182,7 @@ tag_invoke(
ASSERT(!name.empty(), "Field name is empty after stripping 'ID'");
}
// ValidPseudoAccounts invariant guarantees that only one field can be set
jv.as_object()[JS(pseudo_account)].as_object()[JS(type)] = name;
jv.as_object()[JS(pseudo_account)] = boost::json::object{{JS(type), name}};
break;
}
}

View File

@@ -40,13 +40,13 @@ struct ClusterBackendTest : util::prometheus::WithPrometheus, MockBackendTestStr
testing::StrictMock<
testing::MockFunction<void(ClioNode::CUuid, std::shared_ptr<Backend::ClusterData const>)>>
callbackMock;
std::binary_semaphore semaphore{0};
std::counting_semaphore<> semaphore{0};
class SemaphoreReleaseGuard {
std::binary_semaphore& semaphore_;
std::counting_semaphore<>& semaphore_;
public:
SemaphoreReleaseGuard(std::binary_semaphore& s) : semaphore_(s)
SemaphoreReleaseGuard(std::counting_semaphore<>& s) : semaphore_(s)
{
}
~SemaphoreReleaseGuard()
@@ -72,7 +72,7 @@ TEST_F(ClusterBackendTest, SubscribeToNewState)
EXPECT_CALL(*backend_, fetchClioNodesData)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(BackendInterface::ClioNodesDataFetchResult{}));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AtLeast(1));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AnyNumber());
EXPECT_CALL(writerStateRef, isReadOnly)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(true));
@@ -151,7 +151,7 @@ TEST_F(ClusterBackendTest, FetchClioNodesDataThrowsException)
EXPECT_CALL(*backend_, fetchClioNodesData)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Throw(std::runtime_error("Database connection failed")));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AtLeast(1));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AnyNumber());
EXPECT_CALL(writerStateRef, isReadOnly)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(true));
@@ -208,7 +208,7 @@ TEST_F(ClusterBackendTest, FetchClioNodesDataReturnsDataWithOtherNodes)
}
)
);
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AtLeast(1));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AnyNumber());
EXPECT_CALL(writerStateRef, isReadOnly)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(false));
@@ -286,7 +286,7 @@ TEST_F(ClusterBackendTest, FetchClioNodesDataReturnsOnlySelfData)
}
};
});
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AtLeast(1));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AnyNumber());
EXPECT_CALL(writerStateRef, isReadOnly)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(true));
@@ -342,7 +342,7 @@ TEST_F(ClusterBackendTest, FetchClioNodesDataReturnsInvalidJson)
}
)
);
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AtLeast(1));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AnyNumber());
EXPECT_CALL(writerStateRef, isReadOnly)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(true));
@@ -398,7 +398,7 @@ TEST_F(ClusterBackendTest, FetchClioNodesDataReturnsValidJsonButCannotConvertToC
}
)
);
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AtLeast(1));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AnyNumber());
EXPECT_CALL(writerStateRef, isReadOnly)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(true));
@@ -536,7 +536,7 @@ TEST_F(ClusterBackendTest, SubscribeToNewStateReflectsCacheIsCurrentlyLoading)
EXPECT_CALL(*backend_, fetchClioNodesData)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(BackendInterface::ClioNodesDataFetchResult{}));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AtLeast(1));
EXPECT_CALL(*backend_, writeNodeMessage).Times(testing::AnyNumber());
EXPECT_CALL(writerStateRef, isReadOnly)
.Times(testing::AtLeast(1))
.WillRepeatedly(testing::Return(true));

View File

@@ -437,6 +437,80 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsTrueV2)
});
}
TEST_F(RPCAccountInfoHandlerTest, PseudoAccountReportsType)
{
auto const expectedOutput = fmt::format(
R"JSON({{
"account_data": {{
"Account": "{}",
"AMMID": "{}",
"Balance": "200",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 2,
"PreviousTxnID": "{}",
"PreviousTxnLgrSeq": 2,
"Sequence": 2,
"TransferRate": 0,
"index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8"
}},
"account_flags": {{
"defaultRipple": false,
"depositAuth": false,
"disableMasterKey": false,
"disallowIncomingXRP": false,
"globalFreeze": false,
"noFreeze": false,
"passwordSpent": false,
"requireAuthorization": false,
"requireDestinationTag": false
}},
"pseudo_account": {{
"type": "AMM"
}},
"ledger_hash": "{}",
"ledger_index": 30,
"validated": true
}})JSON",
kAccount,
kIndex1,
kIndex1,
kLedgerHash
);
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30);
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
auto const account = getAccountIdWithString(kAccount);
auto const accountKk = xrpl::keylet::account(account).key;
auto const accountRoot =
createAccountRootObject(kAccount, 0, 2, 200, 2, kIndex1, 2, 0, xrpl::uint256{kIndex1});
ON_CALL(*backend_, doFetchLedgerObject(accountKk, 30, _))
.WillByDefault(Return(accountRoot.getSerializer().peekData()));
EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::DisallowIncoming, _))
.WillOnce(Return(false));
EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::Clawback, _))
.WillOnce(Return(false));
EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::TokenEscrow, _))
.WillOnce(Return(false));
EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1);
static auto const kInput = boost::json::parse(
fmt::format(
R"JSON({{
"account": "{}"
}})JSON",
kAccount
)
);
auto const handler = AnyHandler{AccountInfoHandler{backend_, mockAmendmentCenterPtr_}};
runSpawn([&](auto yield) {
auto const output = handler.process(kInput, Context{.yield = yield, .apiVersion = 2});
ASSERT_TRUE(output);
EXPECT_EQ(*output.result, boost::json::parse(expectedOutput));
});
}
TEST_F(RPCAccountInfoHandlerTest, SignerListsTrueV1)
{
auto const expectedOutput = fmt::format(

View File

@@ -2025,6 +2025,38 @@ generateTestValuesForParametersTest()
.expectedError = "malformedRequest",
.expectedErrorMessage = "Malformed request."
},
ParamTestCaseBundle{
.testName = "CredentialCredentialTypeNotHex",
.testJson = fmt::format(
R"JSON({{
"credential": {{
"subject": "{}",
"issuer": "{}",
"credential_type": "hello world"
}}
}})JSON",
kAccount,
kAccount2
),
.expectedError = "malformedAuthorizedCredentials",
.expectedErrorMessage = "credential_type NotHexString"
},
ParamTestCaseBundle{
.testName = "CredentialCredentialTypeEmpty",
.testJson = fmt::format(
R"JSON({{
"credential": {{
"subject": "{}",
"issuer": "{}",
"credential_type": ""
}}
}})JSON",
kAccount,
kAccount2
),
.expectedError = "malformedAuthorizedCredentials",
.expectedErrorMessage = "credential_type is empty"
},
ParamTestCaseBundle{
.testName = "InvalidMPTokenAccount",
.testJson = fmt::format(