-
Notifications
You must be signed in to change notification settings - Fork 0
feat(s4): domain model unit tests — ChainTransfer, WalletBalance, VOs (STA-133) #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f929576
8297795
fa26ec9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,166 @@ | ||||||||||||||||||||||||||||||||||||||||
| package com.stablecoin.payments.custody.domain.model; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.DisplayName; | ||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.Nested; | ||||||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.Test; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||||||||||||||||||||||||||||||||||||||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @DisplayName("ChainConfig") | ||||||||||||||||||||||||||||||||||||||||
| class ChainConfigTest { | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private static final ChainId BASE = new ChainId("base"); | ||||||||||||||||||||||||||||||||||||||||
| private static final List<String> RPC_ENDPOINTS = List.of("https://rpc.base.org", "https://rpc2.base.org"); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Nested | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("Valid Construction") | ||||||||||||||||||||||||||||||||||||||||
| class ValidConstruction { | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("creates chain config with all fields") | ||||||||||||||||||||||||||||||||||||||||
| void createsChainConfig() { | ||||||||||||||||||||||||||||||||||||||||
| var result = new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, "ETH", RPC_ENDPOINTS, "https://basescan.org" | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| var expected = new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, "ETH", RPC_ENDPOINTS, "https://basescan.org" | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| assertThat(result).usingRecursiveComparison().isEqualTo(expected); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Tautological test — comparing identically-constructed objects. Same issue as TransferParticipantTest. Assert on individual accessors to verify field population. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("accepts zero minConfirmations") | ||||||||||||||||||||||||||||||||||||||||
| void acceptsZeroConfirmations() { | ||||||||||||||||||||||||||||||||||||||||
| var config = new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 0, 2, "ETH", RPC_ENDPOINTS, "https://basescan.org" | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assertThat(config.minConfirmations()).isZero(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("accepts zero avgFinalitySeconds") | ||||||||||||||||||||||||||||||||||||||||
| void acceptsZeroFinality() { | ||||||||||||||||||||||||||||||||||||||||
| var config = new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 0, "ETH", RPC_ENDPOINTS, "https://basescan.org" | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assertThat(config.avgFinalitySeconds()).isZero(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("accepts null explorerUrl") | ||||||||||||||||||||||||||||||||||||||||
| void acceptsNullExplorerUrl() { | ||||||||||||||||||||||||||||||||||||||||
| var config = new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, "ETH", RPC_ENDPOINTS, null | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assertThat(config.explorerUrl()).isNull(); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("accepts single RPC endpoint") | ||||||||||||||||||||||||||||||||||||||||
| void acceptsSingleEndpoint() { | ||||||||||||||||||||||||||||||||||||||||
| var config = new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, "ETH", List.of("https://rpc.base.org"), null | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assertThat(config.rpcEndpoints()).hasSize(1); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Nested | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("Validation") | ||||||||||||||||||||||||||||||||||||||||
| class Validation { | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rejects null chainId") | ||||||||||||||||||||||||||||||||||||||||
| void rejectsNullChainId() { | ||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| null, 12, 2, "ETH", RPC_ENDPOINTS, null | ||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||
| .hasMessage("Chain ID is required"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rejects negative minConfirmations") | ||||||||||||||||||||||||||||||||||||||||
| void rejectsNegativeConfirmations() { | ||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, -1, 2, "ETH", RPC_ENDPOINTS, null | ||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||
| .hasMessage("Min confirmations must be non-negative"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rejects negative avgFinalitySeconds") | ||||||||||||||||||||||||||||||||||||||||
| void rejectsNegativeFinality() { | ||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, -1, "ETH", RPC_ENDPOINTS, null | ||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||
| .hasMessage("Avg finality seconds must be non-negative"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rejects null nativeToken") | ||||||||||||||||||||||||||||||||||||||||
| void rejectsNullNativeToken() { | ||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, null, RPC_ENDPOINTS, null | ||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||
| .hasMessage("Native token is required"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rejects blank nativeToken") | ||||||||||||||||||||||||||||||||||||||||
| void rejectsBlankNativeToken() { | ||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, " ", RPC_ENDPOINTS, null | ||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||
| .hasMessage("Native token is required"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rejects null rpcEndpoints") | ||||||||||||||||||||||||||||||||||||||||
| void rejectsNullEndpoints() { | ||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, "ETH", null, null | ||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||
| .hasMessage("At least one RPC endpoint is required"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rejects empty rpcEndpoints") | ||||||||||||||||||||||||||||||||||||||||
| void rejectsEmptyEndpoints() { | ||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> new ChainConfig( | ||||||||||||||||||||||||||||||||||||||||
| BASE, 12, 2, "ETH", List.of(), null | ||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(IllegalArgumentException.class) | ||||||||||||||||||||||||||||||||||||||||
| .hasMessage("At least one RPC endpoint is required"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Nested | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("Defensive Copy") | ||||||||||||||||||||||||||||||||||||||||
| class DefensiveCopy { | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| @Test | ||||||||||||||||||||||||||||||||||||||||
| @DisplayName("rpcEndpoints are defensively copied") | ||||||||||||||||||||||||||||||||||||||||
| void endpointsDefensivelyCopied() { | ||||||||||||||||||||||||||||||||||||||||
| var mutableList = new ArrayList<>(List.of("https://rpc.base.org")); | ||||||||||||||||||||||||||||||||||||||||
| var config = new ChainConfig(BASE, 12, 2, "ETH", mutableList, null); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| assertThatThrownBy(() -> config.rpcEndpoints().add("https://evil.com")) | ||||||||||||||||||||||||||||||||||||||||
| .isInstanceOf(UnsupportedOperationException.class); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+156
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Defensive copy test is incomplete. This only verifies the returned list is unmodifiable. A proper defensive copy test should also verify that mutating the input list after construction doesn't affect the internal state. Suggested enhancement `@Test`
- `@DisplayName`("rpcEndpoints are defensively copied")
- void endpointsDefensivelyCopied() {
+ `@DisplayName`("rpcEndpoints are defensively copied on input")
+ void inputMutationDoesNotAffectConfig() {
var mutableList = new ArrayList<>(List.of("https://rpc.base.org"));
var config = new ChainConfig(BASE, 12, 2, "ETH", mutableList, null);
+
+ mutableList.add("https://malicious.com");
- assertThatThrownBy(() -> config.rpcEndpoints().add("https://evil.com"))
- .isInstanceOf(UnsupportedOperationException.class);
+ assertThat(config.rpcEndpoints()).containsExactly("https://rpc.base.org");
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| package com.stablecoin.payments.custody.domain.model; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix Spotless formatting violations. Pipeline failed with ./gradlew spotlessApply🧰 Tools🪛 GitHub Actions: CI[error] 1-1: spotlessJavaCheck failed. Formatting violations detected in src/test/java/com/stablecoin/payments/custody/domain/model/ChainIdTest.java. Run './gradlew spotlessApply' to fix code style issues. 🤖 Prompt for AI Agents |
||
|
|
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.ValueSource; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
|
||
| @DisplayName("ChainId") | ||
| class ChainIdTest { | ||
|
|
||
| @Nested | ||
| @DisplayName("Valid Chains") | ||
| class ValidChains { | ||
|
|
||
| @Test | ||
| @DisplayName("accepts 'ethereum'") | ||
| void acceptsEthereum() { | ||
| var chainId = new ChainId("ethereum"); | ||
| assertThat(chainId.value()).isEqualTo("ethereum"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("accepts 'solana'") | ||
| void acceptsSolana() { | ||
| var chainId = new ChainId("solana"); | ||
| assertThat(chainId.value()).isEqualTo("solana"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("accepts 'stellar'") | ||
| void acceptsStellar() { | ||
| var chainId = new ChainId("stellar"); | ||
| assertThat(chainId.value()).isEqualTo("stellar"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("accepts 'tron'") | ||
| void acceptsTron() { | ||
| var chainId = new ChainId("tron"); | ||
| assertThat(chainId.value()).isEqualTo("tron"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("accepts 'base'") | ||
| void acceptsBase() { | ||
| var chainId = new ChainId("base"); | ||
| assertThat(chainId.value()).isEqualTo("base"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("accepts 'polygon'") | ||
| void acceptsPolygon() { | ||
| var chainId = new ChainId("polygon"); | ||
| assertThat(chainId.value()).isEqualTo("polygon"); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("accepts 'avalanche'") | ||
| void acceptsAvalanche() { | ||
| var chainId = new ChainId("avalanche"); | ||
| assertThat(chainId.value()).isEqualTo("avalanche"); | ||
| } | ||
|
Comment on lines
+19
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider parameterized test for valid chains. Seven near-identical tests could be consolidated: Suggested refactor`@ParameterizedTest`
`@ValueSource`(strings = {"ethereum", "solana", "stellar", "tron", "base", "polygon", "avalanche"})
`@DisplayName`("accepts supported chain")
void acceptsSupportedChain(String chain) {
var chainId = new ChainId(chain);
assertThat(chainId.value()).isEqualTo(chain);
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| @Nested | ||
| @DisplayName("Invalid Values") | ||
| class InvalidValues { | ||
|
|
||
| @Test | ||
| @DisplayName("rejects null value") | ||
| void rejectsNull() { | ||
| assertThatThrownBy(() -> new ChainId(null)) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("Chain ID value is required"); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @ValueSource(strings = {"", " ", "\t"}) | ||
| @DisplayName("rejects blank values") | ||
| void rejectsBlank(String value) { | ||
| assertThatThrownBy(() -> new ChainId(value)) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("Chain ID value is required"); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @ValueSource(strings = {"bitcoin", "cardano", "ripple", "ETHEREUM", "Base", "SOLANA"}) | ||
| @DisplayName("rejects unsupported chains") | ||
| void rejectsUnsupported(String value) { | ||
| assertThatThrownBy(() -> new ChainId(value)) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessageContaining("Unsupported chain"); | ||
| } | ||
| } | ||
|
|
||
| @Nested | ||
| @DisplayName("Equality") | ||
| class Equality { | ||
|
|
||
| @Test | ||
| @DisplayName("two ChainId with same value are equal") | ||
| void equalWhenSameValue() { | ||
| var a = new ChainId("base"); | ||
| var b = new ChainId("base"); | ||
|
|
||
| assertThat(a).isEqualTo(b); | ||
| assertThat(a.hashCode()).isEqualTo(b.hashCode()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("two ChainId with different values are not equal") | ||
| void notEqualWhenDifferentValue() { | ||
| var a = new ChainId("base"); | ||
| var b = new ChainId("ethereum"); | ||
|
|
||
| assertThat(a).isNotEqualTo(b); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.