|
1 | 1 | package org.hiero.microprofile.test; |
2 | 2 |
|
3 | 3 | import com.hedera.hashgraph.sdk.AccountId; |
4 | | -import jakarta.json.JsonObject; |
5 | | -import java.util.Collections; |
6 | | -import java.util.List; |
7 | | -import java.util.function.Function; |
8 | 4 | import org.hiero.base.data.BalanceModification; |
9 | 5 | import org.hiero.base.data.Result; |
10 | | -import org.hiero.base.data.TransactionInfo; |
11 | | -import org.hiero.base.implementation.MirrorNodeJsonConverter; |
12 | | -import org.hiero.base.implementation.MirrorNodeRestClient; |
13 | 6 | import org.hiero.base.protocol.data.TransactionType; |
14 | 7 | import org.hiero.microprofile.implementation.MirrorNodeClientImpl; |
15 | 8 | import org.hiero.microprofile.implementation.MirrorNodeJsonConverterImpl; |
|
18 | 11 | import org.junit.jupiter.api.Test; |
19 | 12 |
|
20 | 13 | /** |
21 | | - * Unit test to verify the MicroProfile MirrorNodeClientImpl constructs the correct |
22 | | - * mirror-node API paths for transaction queries. |
| 14 | + * Unit test to verify the MicroProfile MirrorNodeClientImpl constructs the correct mirror-node API |
| 15 | + * paths for transaction queries. |
23 | 16 | * |
24 | | - * <p>These tests do NOT require a live Hiero network. They verify the URL path |
25 | | - * construction logic by inspecting what the implementation builds before any HTTP call. |
| 17 | + * <p>These tests do NOT require a live Hiero network. They verify the URL path construction logic |
| 18 | + * by inspecting what the implementation builds before any HTTP call. |
26 | 19 | * |
27 | | - * <p>Bug: All four transaction query methods originally used "/api/v1/tokens" (the token |
28 | | - * listing endpoint) instead of "/api/v1/transactions". Additionally, |
29 | | - * queryTransactionsByAccountAndType used the Java enum name (e.g. "ACCOUNT_CREATE") |
30 | | - * instead of the mirror-node protocol string (e.g. "CRYPTOCREATEACCOUNT"). |
| 20 | + * <p>Bug: All four transaction query methods originally used "/api/v1/tokens" (the token listing |
| 21 | + * endpoint) instead of "/api/v1/transactions". Additionally, queryTransactionsByAccountAndType used |
| 22 | + * the Java enum name (e.g. "ACCOUNT_CREATE") instead of the mirror-node protocol string (e.g. |
| 23 | + * "CRYPTOCREATEACCOUNT"). |
31 | 24 | */ |
32 | 25 | public class MirrorNodeClientImplPathTest { |
33 | 26 |
|
34 | | - /** |
35 | | - * A test subclass that captures the path passed to RestBasedPage without making |
36 | | - * any HTTP call. This lets us verify the URL path construction in isolation. |
37 | | - */ |
38 | | - static class PathCapturingMirrorNodeClient extends MirrorNodeClientImpl { |
39 | | - String lastCapturedPath; |
40 | | - |
41 | | - PathCapturingMirrorNodeClient() { |
42 | | - super( |
43 | | - new MirrorNodeRestClientImpl("http://localhost:0"), |
44 | | - new MirrorNodeJsonConverterImpl() |
45 | | - ); |
46 | | - } |
47 | | - } |
48 | | - |
49 | | - /** |
50 | | - * We can't easily intercept the path inside RestBasedPage without reflection, |
51 | | - * but we CAN verify the path construction by looking at what the methods generate. |
52 | | - * Since RestBasedPage immediately makes an HTTP call in its constructor, we test |
53 | | - * the path building logic by replicating just the relevant lines from each method. |
54 | | - * |
55 | | - * This is the same logic as the production code — we verify the string values. |
56 | | - */ |
57 | | - |
58 | | - @Test |
59 | | - void queryTransactionsByAccount_shouldUseTransactionsEndpoint() { |
60 | | - // This is the path the FIXED code builds: |
61 | | - AccountId accountId = AccountId.fromString("0.0.12345"); |
62 | | - String fixedPath = "/api/v1/transactions?account.id=" + accountId; |
63 | | - String brokenPath = "/api/v1/tokens?account.id=" + accountId; |
64 | | - |
65 | | - Assertions.assertTrue(fixedPath.startsWith("/api/v1/transactions"), |
66 | | - "Path must start with /api/v1/transactions, not /api/v1/tokens"); |
67 | | - Assertions.assertFalse(fixedPath.startsWith("/api/v1/tokens"), |
68 | | - "Path must NOT start with /api/v1/tokens"); |
69 | | - Assertions.assertEquals("/api/v1/transactions?account.id=0.0.12345", fixedPath); |
70 | | - |
71 | | - // Show what the broken code did: |
72 | | - Assertions.assertTrue(brokenPath.startsWith("/api/v1/tokens"), |
73 | | - "The broken code incorrectly used /api/v1/tokens"); |
74 | | - } |
75 | | - |
76 | | - @Test |
77 | | - void queryTransactionsByAccountAndType_shouldUseGetType() { |
78 | | - AccountId accountId = AccountId.fromString("0.0.12345"); |
79 | | - TransactionType type = TransactionType.ACCOUNT_CREATE; |
80 | | - |
81 | | - // Fixed: uses type.getType() which returns the mirror-node string |
82 | | - String fixedPath = "/api/v1/transactions?account.id=" + accountId |
83 | | - + "&transactiontype=" + type.getType(); |
84 | | - |
85 | | - // Broken: used type directly (toString/name gives Java enum name) |
86 | | - String brokenPath = "/api/v1/tokens?account.id=" + accountId |
87 | | - + "&transactiontype=" + type; |
88 | | - |
89 | | - // The mirror node expects "CRYPTOCREATEACCOUNT", NOT "ACCOUNT_CREATE" |
90 | | - Assertions.assertEquals( |
91 | | - "/api/v1/transactions?account.id=0.0.12345&transactiontype=CRYPTOCREATEACCOUNT", |
92 | | - fixedPath, |
93 | | - "Must use type.getType() for mirror-node protocol string"); |
94 | | - Assertions.assertTrue(brokenPath.contains("ACCOUNT_CREATE"), |
95 | | - "The broken code sent the Java enum name instead of the protocol string"); |
96 | | - Assertions.assertFalse(fixedPath.contains("ACCOUNT_CREATE"), |
97 | | - "Fixed code must NOT contain the Java enum name ACCOUNT_CREATE"); |
98 | | - } |
99 | | - |
100 | | - @Test |
101 | | - void queryTransactionsByAccountAndResult_shouldUseTransactionsEndpoint() { |
102 | | - AccountId accountId = AccountId.fromString("0.0.12345"); |
103 | | - Result result = Result.SUCCESS; |
104 | | - |
105 | | - String fixedPath = "/api/v1/transactions?account.id=" + accountId |
106 | | - + "&result=" + result.name(); |
107 | | - String brokenPath = "/api/v1/tokens?account.id=" + accountId |
108 | | - + "&result=" + result; |
109 | | - |
110 | | - Assertions.assertEquals( |
111 | | - "/api/v1/transactions?account.id=0.0.12345&result=SUCCESS", |
112 | | - fixedPath); |
113 | | - Assertions.assertTrue(brokenPath.startsWith("/api/v1/tokens"), |
114 | | - "The broken code used /api/v1/tokens"); |
115 | | - } |
116 | | - |
117 | | - @Test |
118 | | - void queryTransactionsByAccountAndModification_shouldUseTransactionsEndpoint() { |
119 | | - AccountId accountId = AccountId.fromString("0.0.12345"); |
120 | | - BalanceModification type = BalanceModification.DEBIT; |
121 | | - |
122 | | - String fixedPath = "/api/v1/transactions?account.id=" + accountId |
123 | | - + "&type=" + type.name(); |
124 | | - String brokenPath = "/api/v1/tokens?account.id=" + accountId |
125 | | - + "&type=" + type; |
126 | | - |
127 | | - Assertions.assertEquals( |
128 | | - "/api/v1/transactions?account.id=0.0.12345&type=DEBIT", |
129 | | - fixedPath); |
130 | | - Assertions.assertTrue(brokenPath.startsWith("/api/v1/tokens"), |
131 | | - "The broken code used /api/v1/tokens"); |
132 | | - } |
| 27 | + /** |
| 28 | + * A test subclass that captures the path passed to RestBasedPage without making any HTTP call. |
| 29 | + * This lets us verify the URL path construction in isolation. |
| 30 | + */ |
| 31 | + static class PathCapturingMirrorNodeClient extends MirrorNodeClientImpl { |
| 32 | + String lastCapturedPath; |
133 | 33 |
|
134 | | - @Test |
135 | | - void transactionTypeGetType_returnsMirrorNodeString_notJavaEnumName() { |
136 | | - // This is the core serialization issue. |
137 | | - // The mirror node API expects the Hedera protocol name, not the Java enum name. |
138 | | - Assertions.assertEquals("CRYPTOCREATEACCOUNT", TransactionType.ACCOUNT_CREATE.getType()); |
139 | | - Assertions.assertEquals("ACCOUNT_CREATE", TransactionType.ACCOUNT_CREATE.name()); |
140 | | - Assertions.assertNotEquals( |
141 | | - TransactionType.ACCOUNT_CREATE.name(), |
142 | | - TransactionType.ACCOUNT_CREATE.getType(), |
143 | | - "getType() must be different from name() — the mirror node uses the protocol string"); |
144 | | - |
145 | | - // More examples to prove the pattern: |
146 | | - Assertions.assertEquals("CRYPTOTRANSFER", TransactionType.CRYPTO_TRANSFER.getType()); |
147 | | - Assertions.assertEquals("CONTRACTCALL", TransactionType.CONTRACT_CALL.getType()); |
148 | | - Assertions.assertEquals("TOKENCREATION", TransactionType.TOKEN_CREATE.getType()); |
| 34 | + PathCapturingMirrorNodeClient() { |
| 35 | + super(new MirrorNodeRestClientImpl("http://localhost:0"), new MirrorNodeJsonConverterImpl()); |
149 | 36 | } |
| 37 | + } |
| 38 | + |
| 39 | + /** |
| 40 | + * We can't easily intercept the path inside RestBasedPage without reflection, but we CAN verify |
| 41 | + * the path construction by looking at what the methods generate. Since RestBasedPage immediately |
| 42 | + * makes an HTTP call in its constructor, we test the path building logic by replicating just the |
| 43 | + * relevant lines from each method. |
| 44 | + * |
| 45 | + * <p>This is the same logic as the production code — we verify the string values. |
| 46 | + */ |
| 47 | + @Test |
| 48 | + void queryTransactionsByAccount_shouldUseTransactionsEndpoint() { |
| 49 | + // This is the path the FIXED code builds: |
| 50 | + AccountId accountId = AccountId.fromString("0.0.12345"); |
| 51 | + String fixedPath = "/api/v1/transactions?account.id=" + accountId; |
| 52 | + String brokenPath = "/api/v1/tokens?account.id=" + accountId; |
| 53 | + |
| 54 | + Assertions.assertTrue( |
| 55 | + fixedPath.startsWith("/api/v1/transactions"), |
| 56 | + "Path must start with /api/v1/transactions, not /api/v1/tokens"); |
| 57 | + Assertions.assertFalse( |
| 58 | + fixedPath.startsWith("/api/v1/tokens"), "Path must NOT start with /api/v1/tokens"); |
| 59 | + Assertions.assertEquals("/api/v1/transactions?account.id=0.0.12345", fixedPath); |
| 60 | + |
| 61 | + // Show what the broken code did: |
| 62 | + Assertions.assertTrue( |
| 63 | + brokenPath.startsWith("/api/v1/tokens"), "The broken code incorrectly used /api/v1/tokens"); |
| 64 | + } |
| 65 | + |
| 66 | + @Test |
| 67 | + void queryTransactionsByAccountAndType_shouldUseGetType() { |
| 68 | + AccountId accountId = AccountId.fromString("0.0.12345"); |
| 69 | + TransactionType type = TransactionType.ACCOUNT_CREATE; |
| 70 | + |
| 71 | + // Fixed: uses type.getType() which returns the mirror-node string |
| 72 | + String fixedPath = |
| 73 | + "/api/v1/transactions?account.id=" + accountId + "&transactiontype=" + type.getType(); |
| 74 | + |
| 75 | + // Broken: used type directly (toString/name gives Java enum name) |
| 76 | + String brokenPath = "/api/v1/tokens?account.id=" + accountId + "&transactiontype=" + type; |
| 77 | + |
| 78 | + // The mirror node expects "CRYPTOCREATEACCOUNT", NOT "ACCOUNT_CREATE" |
| 79 | + Assertions.assertEquals( |
| 80 | + "/api/v1/transactions?account.id=0.0.12345&transactiontype=CRYPTOCREATEACCOUNT", |
| 81 | + fixedPath, |
| 82 | + "Must use type.getType() for mirror-node protocol string"); |
| 83 | + Assertions.assertTrue( |
| 84 | + brokenPath.contains("ACCOUNT_CREATE"), |
| 85 | + "The broken code sent the Java enum name instead of the protocol string"); |
| 86 | + Assertions.assertFalse( |
| 87 | + fixedPath.contains("ACCOUNT_CREATE"), |
| 88 | + "Fixed code must NOT contain the Java enum name ACCOUNT_CREATE"); |
| 89 | + } |
| 90 | + |
| 91 | + @Test |
| 92 | + void queryTransactionsByAccountAndResult_shouldUseTransactionsEndpoint() { |
| 93 | + AccountId accountId = AccountId.fromString("0.0.12345"); |
| 94 | + Result result = Result.SUCCESS; |
| 95 | + |
| 96 | + String fixedPath = "/api/v1/transactions?account.id=" + accountId + "&result=" + result.name(); |
| 97 | + String brokenPath = "/api/v1/tokens?account.id=" + accountId + "&result=" + result; |
| 98 | + |
| 99 | + Assertions.assertEquals("/api/v1/transactions?account.id=0.0.12345&result=SUCCESS", fixedPath); |
| 100 | + Assertions.assertTrue( |
| 101 | + brokenPath.startsWith("/api/v1/tokens"), "The broken code used /api/v1/tokens"); |
| 102 | + } |
| 103 | + |
| 104 | + @Test |
| 105 | + void queryTransactionsByAccountAndModification_shouldUseTransactionsEndpoint() { |
| 106 | + AccountId accountId = AccountId.fromString("0.0.12345"); |
| 107 | + BalanceModification type = BalanceModification.DEBIT; |
| 108 | + |
| 109 | + String fixedPath = "/api/v1/transactions?account.id=" + accountId + "&type=" + type.name(); |
| 110 | + String brokenPath = "/api/v1/tokens?account.id=" + accountId + "&type=" + type; |
| 111 | + |
| 112 | + Assertions.assertEquals("/api/v1/transactions?account.id=0.0.12345&type=DEBIT", fixedPath); |
| 113 | + Assertions.assertTrue( |
| 114 | + brokenPath.startsWith("/api/v1/tokens"), "The broken code used /api/v1/tokens"); |
| 115 | + } |
| 116 | + |
| 117 | + @Test |
| 118 | + void transactionTypeGetType_returnsMirrorNodeString_notJavaEnumName() { |
| 119 | + // This is the core serialization issue. |
| 120 | + // The mirror node API expects the Hedera protocol name, not the Java enum name. |
| 121 | + Assertions.assertEquals("CRYPTOCREATEACCOUNT", TransactionType.ACCOUNT_CREATE.getType()); |
| 122 | + Assertions.assertEquals("ACCOUNT_CREATE", TransactionType.ACCOUNT_CREATE.name()); |
| 123 | + Assertions.assertNotEquals( |
| 124 | + TransactionType.ACCOUNT_CREATE.name(), |
| 125 | + TransactionType.ACCOUNT_CREATE.getType(), |
| 126 | + "getType() must be different from name() — the mirror node uses the protocol string"); |
| 127 | + |
| 128 | + // More examples to prove the pattern: |
| 129 | + Assertions.assertEquals("CRYPTOTRANSFER", TransactionType.CRYPTO_TRANSFER.getType()); |
| 130 | + Assertions.assertEquals("CONTRACTCALL", TransactionType.CONTRACT_CALL.getType()); |
| 131 | + Assertions.assertEquals("TOKENCREATION", TransactionType.TOKEN_CREATE.getType()); |
| 132 | + } |
150 | 133 | } |
0 commit comments