Skip to content

Commit ba1f3df

Browse files
authored
test: Add regression test for update node accountId (#21889)
Signed-off-by: Zhivko Kelchev <[email protected]>
1 parent 15aada6 commit ba1f3df

File tree

3 files changed

+151
-3
lines changed

3 files changed

+151
-3
lines changed

hedera-node/yahcli/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ tasks.test {
6969
}
7070

7171
tasks.register<Test>("testSubprocess") {
72+
testClassesDirs = sourceSets["test"].output.classesDirs
73+
classpath = sourceSets["test"].runtimeClasspath
74+
7275
useJUnitPlatform { includeTags("REGRESSION") }
7376

7477
systemProperty("hapi.spec.initial.port", 25000)

hedera-node/yahcli/src/test/java/com/hedera/services/yahcli/test/bdd/YahcliCallOperation.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import java.nio.file.Path;
1515
import java.util.Optional;
1616
import java.util.function.Consumer;
17+
import org.apache.logging.log4j.LogManager;
18+
import org.apache.logging.log4j.Logger;
1719
import org.junit.jupiter.api.Assertions;
1820
import picocli.CommandLine;
1921

@@ -22,6 +24,8 @@
2224
* the {@link SubProcessNetwork} targeted by the containing spec.
2325
*/
2426
public class YahcliCallOperation extends AbstractYahcliOperation<YahcliCallOperation> {
27+
static final Logger log = LogManager.getLogger(YahcliCallOperation.class);
28+
2529
private final String[] args;
2630

2731
@Nullable
@@ -31,6 +35,8 @@ public class YahcliCallOperation extends AbstractYahcliOperation<YahcliCallOpera
3135

3236
private boolean schedule = false;
3337

38+
private boolean expectFail = false;
39+
3440
public YahcliCallOperation(@NonNull final String[] args) {
3541
this.args = requireNonNull(args);
3642
}
@@ -63,6 +69,17 @@ public YahcliCallOperation schedule() {
6369
return this;
6470
}
6571

72+
/**
73+
* Indicates that the Yahcli command is expected to fail when executed.
74+
* If set, a non-zero exit code will be treated as a valid outcome rather than a test failure.
75+
*
76+
* @return this {@link YahcliCallOperation} instance for method chaining
77+
*/
78+
public YahcliCallOperation expectFail() {
79+
this.expectFail = true;
80+
return this;
81+
}
82+
6683
@Override
6784
protected YahcliCallOperation self() {
6885
return this;
@@ -96,8 +113,13 @@ public Optional<Throwable> execFor(@NonNull final HapiSpec spec) {
96113
}
97114
final int rc = commandLine.execute(finalizedArgs);
98115
if (rc != 0) {
99-
Assertions.fail(
100-
"Yahcli command <<" + String.join(" ", finalizedArgs) + ">> failed with exit code " + rc);
116+
final var msg =
117+
"Yahcli command <<" + String.join(" ", finalizedArgs) + ">> failed with exit code " + rc;
118+
if (expectFail) {
119+
log.error(msg);
120+
} else {
121+
Assertions.fail(msg);
122+
}
101123
}
102124
if (outputPath != null) {
103125
final var output = Files.readString(outputPath);

hedera-node/yahcli/src/test/java/com/hedera/services/yahcli/test/regression/NodesCommandsTest.java

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
package com.hedera.services.yahcli.test.regression;
33

44
import static com.hedera.services.bdd.spec.HapiSpec.hapiTest;
5+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate;
6+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate;
57
import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor;
68
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doingContextual;
79
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed;
10+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding;
811
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcingContextual;
912
import static com.hedera.services.yahcli.test.YahcliTestBase.REGRESSION;
1013
import static com.hedera.services.yahcli.test.bdd.YahcliVerbs.asYcDefaultNetworkKey;
@@ -14,7 +17,9 @@
1417
import static org.junit.jupiter.api.Assertions.assertTrue;
1518

1619
import com.hedera.services.bdd.junit.HapiTest;
20+
import com.hedera.services.bdd.junit.LeakyHapiTest;
1721
import com.hedera.services.bdd.spec.keys.SigControl;
22+
import com.hedera.services.bdd.spec.utilops.ContextualActionOp;
1823
import java.util.concurrent.atomic.AtomicLong;
1924
import java.util.stream.Stream;
2025
import org.junit.jupiter.api.DynamicTest;
@@ -23,10 +28,12 @@
2328
@Tag(REGRESSION)
2429
public class NodesCommandsTest {
2530

31+
private int uniqueAdminKeyCounter = 0;
32+
2633
@HapiTest
2734
final Stream<DynamicTest> basicNodeCommandsTest() {
2835
final var newNodeNum = new AtomicLong();
29-
final var adminKey = "adminKey";
36+
final var adminKey = getUniqueAdminKey();
3037
final var adminKeyFileName = adminKey + ".pem";
3138
final var certFilePath = loadResourceFile("testFiles/s-public-node1.pem");
3239
return hapiTest(
@@ -70,4 +77,120 @@ final Stream<DynamicTest> basicNodeCommandsTest() {
7077
.exposingOutputTo(output -> assertTrue(
7178
output.contains("node" + newNodeNum.get() + " has been deleted")))))));
7279
}
80+
81+
@LeakyHapiTest(overrides = {"nodes.updateAccountIdAllowed"})
82+
final Stream<DynamicTest> updateNodeAccountIdCommandTest() {
83+
final var newNodeNum = new AtomicLong();
84+
final var zeroBalanceAccNum = new AtomicLong();
85+
final var newAccNum = new AtomicLong();
86+
87+
final var adminKey = getUniqueAdminKey();
88+
final var adminKeyFileName = adminKey + ".pem";
89+
90+
final var zeroBalanceAccount = "zeroBalanceAccount";
91+
final var newAccount = "newAccount";
92+
93+
final var certFilePath = loadResourceFile("testFiles/s-public-node1.pem");
94+
return hapiTest(
95+
overriding("nodes.updateAccountIdAllowed", "true"),
96+
newKeyNamed(adminKey)
97+
.shape(SigControl.ED25519_ON)
98+
.exportingTo(() -> asYcDefaultNetworkKey(adminKeyFileName), "keypass"),
99+
// create new account with 0 balance
100+
cryptoCreate(zeroBalanceAccount)
101+
.balance(0L)
102+
.exposingCreatedIdTo(id -> zeroBalanceAccNum.set(id.getAccountNum())),
103+
saveAccountKeyToFile(zeroBalanceAccount),
104+
// create account with positive balance
105+
cryptoCreate(newAccount)
106+
.balance(100_000_000L)
107+
.exposingCreatedIdTo(id -> newAccNum.set(id.getAccountNum())),
108+
saveAccountKeyToFile(newAccount),
109+
110+
// Create new node
111+
doingContextual(spec -> allRunFor(
112+
spec,
113+
yahcliNodes(
114+
"create",
115+
"-a",
116+
"23",
117+
"-d",
118+
"Test node",
119+
"-k",
120+
asYcDefaultNetworkKey(adminKeyFileName),
121+
// We are using the full option name here, as -c overrides the config location
122+
"--gossipCaCertificate",
123+
certFilePath.toString(),
124+
"-h",
125+
certFilePath.toString(),
126+
"-g",
127+
"127.0.0.1:50211",
128+
"-s",
129+
"a.b.com:50212")
130+
.exposingOutputTo(newNodeCapturer(newNodeNum::set)))),
131+
132+
// Update the node
133+
doingContextual(spec -> allRunFor(
134+
spec,
135+
// Try to update node accountId with 0 balance
136+
yahcliNodes(
137+
"update",
138+
"-n",
139+
Long.toString(newNodeNum.get()),
140+
"-a",
141+
Long.toString(zeroBalanceAccNum.get()),
142+
"-k",
143+
asYcDefaultNetworkKey(adminKeyFileName),
144+
"-d",
145+
"Updated test node with O balance account id should fail")
146+
.expectFail()
147+
.exposingOutputTo(output -> {
148+
assertTrue(output.contains("FAILED to update node" + newNodeNum.get()));
149+
}),
150+
// Update the node with accountId with positive balance
151+
yahcliNodes(
152+
"update",
153+
"-n",
154+
Long.toString(newNodeNum.get()),
155+
"-a",
156+
Long.toString(newAccNum.get()),
157+
"-k",
158+
asYcDefaultNetworkKey(adminKeyFileName),
159+
"-d",
160+
"Update node with positive balance account id")
161+
.exposingOutputTo(output ->
162+
assertTrue(output.contains("node" + newNodeNum.get() + " has been updated"))),
163+
164+
// Finally delete the just created node
165+
yahcliNodes("delete", "-n", Long.toString(newNodeNum.get()))
166+
.exposingOutputTo(output -> assertTrue(
167+
output.contains("node" + newNodeNum.get() + " has been deleted"))))));
168+
}
169+
170+
// Helpers
171+
172+
private String getUniqueAdminKey() {
173+
uniqueAdminKeyCounter++;
174+
return "adminKey_" + uniqueAdminKeyCounter;
175+
}
176+
177+
private String getAccountKeyFileName(Long accountNum) {
178+
return "account" + accountNum + ".pem";
179+
}
180+
181+
private ContextualActionOp saveAccountKeyToFile(String account) {
182+
final var accountKey = account + "Key";
183+
184+
return doingContextual(spec -> {
185+
final var accountId = spec.registry().getAccountID(account);
186+
final var accountKeyFileName = getAccountKeyFileName(accountId.getAccountNum());
187+
allRunFor(
188+
spec,
189+
// create new key and export it to file
190+
newKeyNamed(accountKey)
191+
.shape(SigControl.ED25519_ON)
192+
.exportingTo(() -> asYcDefaultNetworkKey(accountKeyFileName), "keypass"),
193+
cryptoUpdate(account).key(accountKey));
194+
});
195+
}
73196
}

0 commit comments

Comments
 (0)