Skip to content

Commit b4107fb

Browse files
committed
Increase CodeCov
Signed-off-by: David Fuelling <sappenin@gmail.com>
1 parent 0815b12 commit b4107fb

File tree

7 files changed

+922
-0
lines changed

7 files changed

+922
-0
lines changed

xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/XrplBinaryCodec.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ public UnsignedByteArray encodeForBatchInnerSigning(Batch batch) throws JsonProc
170170

171171
return signableBytes;
172172
} catch (JsonProcessingException e) {
173+
// Test Coverage Note: this catch block is for defensive error handling and is otherwise challenging to test
174+
// in a unit test without mocking static fields or using reflection to create malformed objects, which would
175+
// not be representative of real usage scenarios. In practice, JsonProcessingException should never be thrown
176+
// during normal operation with valid objects, which immutables typically will enforce.
173177
throw new RuntimeException(e.getMessage(), e);
174178
}
175179
}

xrpl4j-core/src/test/java/org/xrpl/xrpl4j/codec/binary/XrplBinaryCodecTest.java

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,29 @@ void encodeForMultiSigning() throws JsonProcessingException {
439439
assertThat(encoder.encodeForMultiSigning(json, signerAccountId)).isEqualTo(expected);
440440
}
441441

442+
@Test
443+
void encodeForMultiSigningWithNonObjectJsonThrowsException() {
444+
String signerAccountId = "rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN";
445+
446+
// Test with JSON array
447+
String jsonArray = "[\"value1\", \"value2\"]";
448+
Assertions.assertThatThrownBy(() -> encoder.encodeForMultiSigning(jsonArray, signerAccountId))
449+
.isInstanceOf(IllegalArgumentException.class)
450+
.hasMessage("JSON object required for signing");
451+
452+
// Test with JSON primitive (string)
453+
String jsonString = "\"just a string\"";
454+
Assertions.assertThatThrownBy(() -> encoder.encodeForMultiSigning(jsonString, signerAccountId))
455+
.isInstanceOf(IllegalArgumentException.class)
456+
.hasMessage("JSON object required for signing");
457+
458+
// Test with JSON primitive (number)
459+
String jsonNumber = "12345";
460+
Assertions.assertThatThrownBy(() -> encoder.encodeForMultiSigning(jsonNumber, signerAccountId))
461+
.isInstanceOf(IllegalArgumentException.class)
462+
.hasMessage("JSON object required for signing");
463+
}
464+
442465
@Test
443466
public void encodePaymentWithSigners() throws JsonProcessingException {
444467
String json = "{\"Account\":\"rGs8cFHMfJanAXVtn6e8Lz2iH8FtnGdexw\",\"Fee\":\"30\"," +
@@ -602,6 +625,158 @@ void encodeForBatchSigningNullBatchInnerThrowsException() {
602625
.isInstanceOf(NullPointerException.class);
603626
}
604627

628+
///////////////////
629+
// encodeForBatchInnerMultiSigning
630+
///////////////////
631+
632+
@Test
633+
void encodeForBatchInnerMultiSigningWithNullBatch() {
634+
Address signerAddress = Address.of("rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN");
635+
Assertions.assertThatThrownBy(() -> encoder.encodeForBatchInnerMultiSigning(null, signerAddress))
636+
.isInstanceOf(NullPointerException.class);
637+
}
638+
639+
@Test
640+
void encodeForBatchInnerMultiSigningWithNullSignerAddress() throws JsonProcessingException {
641+
List<RawTransactionWrapper> innerTransactions = createInnerPayments(2);
642+
Batch batch = createBatch(BatchFlags.ALL_OR_NOTHING, innerTransactions);
643+
644+
Assertions.assertThatThrownBy(() -> encoder.encodeForBatchInnerMultiSigning(batch, null))
645+
.isInstanceOf(NullPointerException.class);
646+
}
647+
648+
@Test
649+
void encodeForBatchInnerMultiSigningWithValidBatch() throws JsonProcessingException {
650+
// Create two inner payment transactions
651+
Payment innerPayment1 = createInnerPayment(XrpCurrencyAmount.ofDrops(1000), UnsignedInteger.ONE);
652+
Payment innerPayment2 = createInnerPayment(XrpCurrencyAmount.ofDrops(2000), UnsignedInteger.valueOf(2));
653+
654+
// Create a Batch transaction
655+
Batch batch = Batch.builder()
656+
.account(BATCH_ACCOUNT)
657+
.fee(XrpCurrencyAmount.ofDrops(100))
658+
.sequence(UnsignedInteger.ONE)
659+
.flags(BatchFlags.ALL_OR_NOTHING)
660+
.signingPublicKey(BATCH_ACCOUNT_SIGNING_PUB_KEY)
661+
.addRawTransactions(
662+
RawTransactionWrapper.of(innerPayment1),
663+
RawTransactionWrapper.of(innerPayment2)
664+
)
665+
.build();
666+
667+
Address signerAddress = Address.of("rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN");
668+
UnsignedByteArray result = encoder.encodeForBatchInnerMultiSigning(batch, signerAddress);
669+
670+
// Verify the result starts with the batch signature prefix "BCH\0" = 0x42434800
671+
assertThat(result.hexValue()).startsWith("42434800");
672+
673+
// Verify the structure: prefix (4 bytes) + flags (4 bytes) + count (4 bytes) + tx IDs (32 bytes * 2) + account ID (20 bytes)
674+
// Total: 4 + 4 + 4 + (32 * 2) + 20 = 96 bytes = 192 hex chars
675+
assertThat(result.hexValue()).hasSize(192);
676+
677+
// Verify flags are encoded correctly (ALL_OR_NOTHING = 0x00010000)
678+
String flagsHex = result.hexValue().substring(8, 16);
679+
assertThat(flagsHex).isEqualTo("00010000");
680+
681+
// Verify count is 2
682+
String countHex = result.hexValue().substring(16, 24);
683+
assertThat(countHex).isEqualTo("00000002");
684+
685+
// Verify the account ID suffix is appended (last 40 hex chars = 20 bytes)
686+
String accountIdSuffix = result.hexValue().substring(152, 192);
687+
assertThat(accountIdSuffix).hasSize(40);
688+
}
689+
690+
@Test
691+
void encodeForBatchInnerMultiSigningWithDifferentSigners() throws JsonProcessingException {
692+
List<RawTransactionWrapper> innerTransactions = createInnerPayments(2);
693+
Batch batch = createBatch(BatchFlags.ALL_OR_NOTHING, innerTransactions);
694+
695+
Address signer1 = Address.of("rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN");
696+
Address signer2 = Address.of("rDgZZ3wyprx4ZqrGQUkquE9Fs2Xs8XBcdw");
697+
698+
UnsignedByteArray result1 = encoder.encodeForBatchInnerMultiSigning(batch, signer1);
699+
UnsignedByteArray result2 = encoder.encodeForBatchInnerMultiSigning(batch, signer2);
700+
701+
// The batch serialization part should be the same (first 152 hex chars)
702+
assertThat(result1.hexValue().substring(0, 152)).isEqualTo(result2.hexValue().substring(0, 152));
703+
704+
// But the account ID suffix should be different (last 40 hex chars)
705+
assertThat(result1.hexValue().substring(152)).isNotEqualTo(result2.hexValue().substring(152));
706+
}
707+
708+
///////////////////
709+
// encodeForSigningClaim
710+
///////////////////
711+
712+
@Test
713+
void encodeForSigningClaimWithNonObjectJson() {
714+
// Test with JSON array
715+
String jsonArray = "[\"value1\", \"value2\"]";
716+
Assertions.assertThatThrownBy(() -> encoder.encodeForSigningClaim(jsonArray))
717+
.isInstanceOf(IllegalArgumentException.class)
718+
.hasMessage("JSON object required for signing");
719+
720+
// Test with JSON primitive
721+
String jsonString = "\"just a string\"";
722+
Assertions.assertThatThrownBy(() -> encoder.encodeForSigningClaim(jsonString))
723+
.isInstanceOf(IllegalArgumentException.class)
724+
.hasMessage("JSON object required for signing");
725+
}
726+
727+
@Test
728+
void encodeForSigningClaimWithMissingChannel() {
729+
String jsonMissingChannel = "{\"Amount\":\"1000\"}";
730+
Assertions.assertThatThrownBy(() -> encoder.encodeForSigningClaim(jsonMissingChannel))
731+
.isInstanceOf(IllegalArgumentException.class)
732+
.hasMessage("Unsigned claims must have Channel and Amount fields.");
733+
}
734+
735+
@Test
736+
void encodeForSigningClaimWithMissingAmount() {
737+
String jsonMissingAmount = "{\"Channel\":\"5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3\"}";
738+
Assertions.assertThatThrownBy(() -> encoder.encodeForSigningClaim(jsonMissingAmount))
739+
.isInstanceOf(IllegalArgumentException.class)
740+
.hasMessage("Unsigned claims must have Channel and Amount fields.");
741+
}
742+
743+
@Test
744+
void encodeForSigningClaimWithValidData() throws JsonProcessingException {
745+
String channel = "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3";
746+
String amount = "1000";
747+
String json = "{\"Channel\":\"" + channel + "\",\"Amount\":\"" + amount + "\"}";
748+
749+
String result = encoder.encodeForSigningClaim(json);
750+
751+
// Verify the result starts with the payment channel claim signature prefix "CLM\0" = 0x434C4D00
752+
assertThat(result).startsWith("434C4D00");
753+
754+
// Verify the structure: prefix (4 bytes) + channel (32 bytes) + amount (8 bytes)
755+
// Total: 4 + 32 + 8 = 44 bytes = 88 hex chars (prefix is included in the total)
756+
assertThat(result).hasSize(88);
757+
758+
// Verify the channel is encoded correctly (after the prefix)
759+
String encodedChannel = result.substring(8, 72);
760+
assertThat(encodedChannel).isEqualTo(channel);
761+
}
762+
763+
@Test
764+
void encodeForSigningClaimWithDifferentAmounts() throws JsonProcessingException {
765+
String channel = "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3";
766+
767+
String json1 = "{\"Channel\":\"" + channel + "\",\"Amount\":\"1000\"}";
768+
String json2 = "{\"Channel\":\"" + channel + "\",\"Amount\":\"2000\"}";
769+
770+
String result1 = encoder.encodeForSigningClaim(json1);
771+
String result2 = encoder.encodeForSigningClaim(json2);
772+
773+
// The prefix and channel should be the same
774+
assertThat(result1.substring(0, 72)).isEqualTo(result2.substring(0, 72));
775+
776+
// But the amount should be different
777+
assertThat(result1.substring(72)).isNotEqualTo(result2.substring(72));
778+
}
779+
605780
// Helper method to create inner payment transactions
606781
private Payment createInnerPayment(CurrencyAmount amount, UnsignedInteger sequence) {
607782
return Payment.builder()

xrpl4j-core/src/test/java/org/xrpl/xrpl4j/crypto/signing/AbstractSignatureServiceTest.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.xrpl.xrpl4j.crypto.keys.PublicKey;
4343
import org.xrpl.xrpl4j.model.client.channels.UnsignedClaim;
4444
import org.xrpl.xrpl4j.model.ledger.Attestation;
45+
import org.xrpl.xrpl4j.model.transactions.Batch;
4546
import org.xrpl.xrpl4j.model.transactions.Signer;
4647
import org.xrpl.xrpl4j.model.transactions.Transaction;
4748

@@ -65,6 +66,8 @@ public class AbstractSignatureServiceTest {
6566
SingleSignedTransaction<Transaction> signedTransactionMock;
6667
@Mock
6768
Signer signerMock;
69+
@Mock
70+
Batch batchMock;
6871

6972
private AtomicBoolean ed25519VerifyCalled;
7073
private AtomicBoolean secp256k1VerifyCalled;
@@ -82,6 +85,8 @@ public void setUp() throws Exception {
8285
when(signedTransactionMock.unsignedTransaction()).thenReturn(transactionMock);
8386
when(signatureUtilsMock.toSignableBytes(Mockito.<Transaction>any())).thenReturn(UnsignedByteArray.empty());
8487
when(signatureUtilsMock.toMultiSignableBytes(any(), any())).thenReturn(UnsignedByteArray.empty());
88+
when(signatureUtilsMock.toSignableInnerBytes(any())).thenReturn(UnsignedByteArray.empty());
89+
when(signatureUtilsMock.toMultiSignableInnerBytes(any(), any())).thenReturn(UnsignedByteArray.empty());
8590
when(signatureUtilsMock.addSignatureToTransaction(any(), any())).thenReturn(signedTransactionMock);
8691

8792
this.signatureService = new AbstractSignatureService<PrivateKeyable>(signatureUtilsMock) {
@@ -433,4 +438,74 @@ public void ecDsaVerify() {
433438
assertThat(ed25519VerifyCalled.get()).isFalse();
434439
verifyNoMoreInteractions(signatureUtilsMock);
435440
}
441+
442+
///////////////////
443+
// signInner
444+
///////////////////
445+
446+
@Test
447+
public void signInnerWithNullPrivateKey() {
448+
assertThrows(NullPointerException.class, () -> signatureService.signInner(null, batchMock));
449+
}
450+
451+
@Test
452+
public void signInnerWithNullBatch() {
453+
assertThrows(NullPointerException.class,
454+
() -> signatureService.signInner(TestConstants.getEdPrivateKey(), null));
455+
}
456+
457+
@Test
458+
public void signInnerEd25519() {
459+
Signature actualSignature = signatureService.signInner(TestConstants.getEdPrivateKey(), batchMock);
460+
assertThat(actualSignature).isEqualTo(ed25519SignatureMock);
461+
462+
verify(signatureUtilsMock).toSignableInnerBytes(batchMock);
463+
verify(signatureUtilsMock, times(0)).toMultiSignableInnerBytes(any(), any());
464+
verifyNoMoreInteractions(signatureUtilsMock);
465+
}
466+
467+
@Test
468+
public void signInnerSecp256k1() {
469+
Signature actualSignature = signatureService.signInner(TestConstants.getEcPrivateKey(), batchMock);
470+
assertThat(actualSignature).isEqualTo(secp256k1SignatureMock);
471+
472+
verify(signatureUtilsMock).toSignableInnerBytes(batchMock);
473+
verify(signatureUtilsMock, times(0)).toMultiSignableInnerBytes(any(), any());
474+
verifyNoMoreInteractions(signatureUtilsMock);
475+
}
476+
477+
///////////////////
478+
// multiSignInner
479+
///////////////////
480+
481+
@Test
482+
public void multiSignInnerWithNullPrivateKey() {
483+
assertThrows(NullPointerException.class, () -> signatureService.multiSignInner(null, batchMock));
484+
}
485+
486+
@Test
487+
public void multiSignInnerWithNullBatch() {
488+
assertThrows(NullPointerException.class,
489+
() -> signatureService.multiSignInner(TestConstants.getEdPrivateKey(), null));
490+
}
491+
492+
@Test
493+
public void multiSignInnerEd25519() {
494+
Signature actualSignature = signatureService.multiSignInner(TestConstants.getEdPrivateKey(), batchMock);
495+
assertThat(actualSignature).isEqualTo(ed25519SignatureMock);
496+
497+
verify(signatureUtilsMock).toMultiSignableInnerBytes(batchMock, TestConstants.ED_ADDRESS);
498+
verify(signatureUtilsMock, times(0)).toSignableInnerBytes(any());
499+
verifyNoMoreInteractions(signatureUtilsMock);
500+
}
501+
502+
@Test
503+
public void multiSignInnerSecp256k1() {
504+
Signature actualSignature = signatureService.multiSignInner(TestConstants.getEcPrivateKey(), batchMock);
505+
assertThat(actualSignature).isEqualTo(secp256k1SignatureMock);
506+
507+
verify(signatureUtilsMock).toMultiSignableInnerBytes(batchMock, TestConstants.EC_ADDRESS);
508+
verify(signatureUtilsMock, times(0)).toSignableInnerBytes(any());
509+
verifyNoMoreInteractions(signatureUtilsMock);
510+
}
436511
}

0 commit comments

Comments
 (0)