diff --git a/quarkus-cli/pom.xml b/quarkus-cli/pom.xml
index 8824f117d..08a0434c2 100644
--- a/quarkus-cli/pom.xml
+++ b/quarkus-cli/pom.xml
@@ -32,4 +32,20 @@
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+
+
+
+ true
+
+
+
+
diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigEncryptIT.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigEncryptIT.java
new file mode 100644
index 000000000..ebe53a017
--- /dev/null
+++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigEncryptIT.java
@@ -0,0 +1,207 @@
+package io.quarkus.ts.quarkus.cli;
+
+import static io.quarkus.test.bootstrap.config.QuarkusEncryptConfigCommandBuilder.AES_GCM_NO_PADDING_HANDLER_ENC_KEY;
+import static io.quarkus.test.bootstrap.config.QuarkusEncryptConfigCommandBuilder.KeyFormat.base64;
+import static io.quarkus.test.bootstrap.config.QuarkusEncryptConfigCommandBuilder.KeyFormat.plain;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.ENCRYPTED_SECRET_3_PROPERTY;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.UNKNOWN_SECRET_HANDLER_PROPERTY;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_1;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_2;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_3;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_4;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import io.quarkus.test.bootstrap.config.QuarkusConfigCommand;
+import io.quarkus.test.bootstrap.config.QuarkusEncryptConfigCommandBuilder;
+import io.quarkus.test.bootstrap.config.QuarkusEncryptConfigCommandBuilder.EncryptionKeyFormatOpt;
+import io.quarkus.test.bootstrap.config.QuarkusEncryptConfigCommandBuilder.EncryptionKeyOpt;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.scenarios.annotations.DisabledOnNative;
+import io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class) // remember, this is stateful test as well as stateful cmd builder
+@Tag("QUARKUS-3456")
+@Tag("quarkus-cli")
+@QuarkusScenario
+@DisabledOnNative // Only for JVM verification
+public class QuarkusCliConfigEncryptIT {
+
+ private static QuarkusEncryptConfigCommandBuilder encryptBuilder = null;
+ private static String encryptionKey = null;
+
+ @Inject
+ static QuarkusConfigCommand configCommand;
+
+ @BeforeAll
+ public static void beforeAll() {
+ encryptBuilder = configCommand
+ .withSmallRyeConfigCryptoDep()
+ .encryptBuilder()
+ .withSmallRyeConfigSourceKeystoreDep();
+ }
+
+ @Order(1)
+ @Test
+ public void encryptSecret_Base64SecretFormat_GenerateEncryptionKey() {
+ // configured props are tested by EncryptPropertyTest#encryptedSecret_Base64SecretFormat_GeneratedEncryptionKey
+ encryptBuilder
+ .secret(SECRET_1.secret)
+ .executeCommand()
+ .secretConsumer(Assertions::assertNotNull)
+ .storeSecretAsSecretExpression(SECRET_1.propertyName)
+ .generatedKeyConsumer(encKey -> encryptionKey = encKey)
+ .assertCommandOutputContains("""
+ The secret %s was encrypted to
+ """.formatted(SECRET_1.secret))
+ .assertCommandOutputContains("""
+ with the generated encryption key (base64):
+ """);
+ }
+
+ @Order(2)
+ @Test
+ public void encryptSecret_PlainKeyFormat_ExistingEncryptionKey() {
+ // configured props are tested by EncryptPropertyTest#encryptSecret_PlainKeyFormat_ExistingEncryptionKey
+ encryptBuilder
+ .encryptionKeyFormat(plain)
+ .encryptionKeyFormatOpt(EncryptionKeyFormatOpt.SHORT)
+ .encryptionKey(encryptionKey)
+ .encryptionKeyOpt(EncryptionKeyOpt.SHORT)
+ .secret(SECRET_2.secret)
+ .executeCommand()
+ .secretConsumer(Assertions::assertNotNull)
+ .storeSecretAsSecretExpression(SECRET_2.propertyName)
+ .assertCommandOutputContains("""
+ The secret %s was encrypted to
+ """.formatted(SECRET_2.secret))
+ .assertCommandOutputNotContains("with the generated encryption key");
+ }
+
+ @Order(3)
+ @Test
+ public void failToDecryptSecretInWrongFormat() {
+ // configured props are tested by EncryptPropertyTest#failToDecryptSecretInWrongFormat
+
+ // prepare secret that is encrypted with a different encryption key than used by Quarkus application
+ // so that unit test can see decryption fails with a wrong property and succeeds with a correct one
+ // also any secret key that is not in "AES" algorithm will fail
+ encryptBuilder
+ .encryptionKeyFormat(base64)
+ .encryptionKeyFormatOpt(EncryptionKeyFormatOpt.LONG)
+ .encryptionKey(SECRET_3.encryptionKey)
+ .encryptionKeyOpt(EncryptionKeyOpt.LONG)
+ .secret(SECRET_3.secret)
+ .doNotSetEncryptionKeyToSecretHandler()
+ .executeCommand()
+ .secretConsumer(Assertions::assertNotNull)
+ .storeSecretAsSecretExpression(SECRET_3.propertyName)
+ .storeSecretAsRawValue(ENCRYPTED_SECRET_3_PROPERTY)
+ .assertCommandOutputContains("""
+ The secret %s was encrypted to
+ """.formatted(SECRET_3.secret))
+ .assertCommandOutputNotContains("with the generated encryption key");
+ }
+
+ @DisabledOnOs(OS.WINDOWS) // Keytool command would require adjustments on Windows
+ @Order(4)
+ @Test
+ public void testKeyStoreConfigSourceWithSecrets() {
+ // configured keystore config source is tested by EncryptPropertyTest#testKeyStoreConfigSourceWithSecrets
+
+ // this tests "Create Keystore" section
+ // see https://quarkus.io/version/main/guides/config-secrets#create-a-keystore
+ // unit test will check that Quarkus app retrieves secret correctly
+ var propertiesKeystoreName = "properties";
+ var propertiesKeystorePwd = "properties-password";
+ var encKeystoreName = "key";
+ var encKeystorePassword = "key-password";
+ // use keystores (one for actual secrets, one for secret encryption key)
+ encryptBuilder.getConfigCommand().addToApplicationPropertiesFile(
+ "smallrye.config.source.keystore.\"properties\".path", propertiesKeystoreName,
+ "smallrye.config.source.keystore.\"properties\".password", propertiesKeystorePwd,
+ "smallrye.config.source.keystore.\"properties\".handler", "aes-gcm-nopadding",
+ "smallrye.config.source.keystore.\"key\".path", encKeystoreName,
+ "smallrye.config.source.keystore.\"key\".password", encKeystorePassword);
+
+ // generate keystores
+ var encKeyBase64Encoded = base64.format(QuarkusEncryptConfigCommandBuilder.generateEncryptionKey());
+ encryptBuilder
+ .encryptionKeyFormat(plain)
+ .encryptionKeyFormatOpt(EncryptionKeyFormatOpt.LONG)
+ .secret(SECRET_4.secret)
+ .encryptionKey(encryptionKey)
+ .executeCommand()
+ .secretConsumer(Assertions::assertNotNull)
+ .secretConsumer(secret -> encryptBuilder
+ .createKeyStore(SECRET_4.propertyName, secret, propertiesKeystoreName, propertiesKeystorePwd)
+ .createKeyStore(AES_GCM_NO_PADDING_HANDLER_ENC_KEY, encKeyBase64Encoded, encKeystoreName,
+ encKeystorePassword))
+ .assertApplicationPropertiesDoesNotContain(SECRET_4.secret)
+ .assertApplicationPropertiesDoesNotContain(SECRET_4.propertyName)
+ .assertApplicationPropertiesDoesNotContain(encKeyBase64Encoded)
+ .assertCommandOutputContains("""
+ The secret %s was encrypted to
+ """.formatted(SECRET_4.secret));
+ }
+
+ @Order(5)
+ @Test
+ public void testWrongSecretKeyHandler() {
+ // configured property is tested by EncryptPropertyTest#testWrongSecretKeyHandler
+
+ // add unknown secret handler so that unit test can assert this is not reported
+ encryptBuilder.getConfigCommand()
+ .addToApplicationPropertiesFile(UNKNOWN_SECRET_HANDLER_PROPERTY, "${unknown-secret-handler::hush-hush}");
+ }
+
+ @Order(6)
+ @Test
+ public void testEncryptCommandHelp() {
+ encryptBuilder
+ .printOutHelp()
+ .assertCommandOutputContains("""
+ Encrypt a Secret value using the AES/GCM/NoPadding algorithm as a default
+ """)
+ .assertCommandOutputContains("""
+ encryption key is generated unless a specific key is set with the --key option
+ """)
+ .assertCommandOutputContains("""
+ Usage: quarkus config encrypt [-eh] [--verbose] [-f=]
+ """)
+ .assertCommandOutputContains("[-k=] SECRET")
+ .assertCommandOutputContains("""
+ The Secret value to encrypt
+ """)
+ .assertCommandOutputContains("-f, --format=")
+ .assertCommandOutputContains("""
+ The Encryption Key Format (base64 / plain)
+ """)
+ .assertCommandOutputContains("""
+ Print more context on errors and exceptions
+ """)
+ .assertCommandOutputContains("""
+ Display this help message
+ """)
+ .assertCommandOutputContains("Verbose mode")
+ .assertCommandOutputContains("-k, --key=")
+ .assertCommandOutputContains("The Encryption Key");
+ }
+
+ @Order(7)
+ @Test
+ public void testQuarkusApplicationWithGeneratedSecrets() {
+ encryptBuilder.getConfigCommand().buildAppAndExpectSuccess(EncryptPropertyTest.class);
+ }
+
+}
diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigRemoveIT.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigRemoveIT.java
new file mode 100644
index 000000000..87aae17ba
--- /dev/null
+++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigRemoveIT.java
@@ -0,0 +1,88 @@
+package io.quarkus.ts.quarkus.cli;
+
+import static io.quarkus.ts.quarkus.cli.config.surefire.RemovePropertyTest.TODO_PROPERTY_NAME;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import io.quarkus.test.bootstrap.config.QuarkusConfigCommand;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.scenarios.annotations.DisabledOnNative;
+import io.quarkus.ts.quarkus.cli.config.surefire.RemovePropertyTest;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class) // remember, this is stateful test as well as stateful cmd builder
+@Tag("QUARKUS-3456")
+@Tag("quarkus-cli")
+@QuarkusScenario
+@DisabledOnNative // Only for JVM verification
+public class QuarkusCliConfigRemoveIT {
+
+ @Inject
+ static QuarkusConfigCommand configCommand;
+
+ @Order(1)
+ @Test
+ public void tryToRemoveProperty() {
+ configCommand
+ .removeProperty()
+ .name(TODO_PROPERTY_NAME)
+ .executeCommand()
+ .assertCommandOutputContains("""
+ Could not find configuration %s
+ """.formatted(TODO_PROPERTY_NAME))
+ .assertApplicationPropertiesDoesNotContain(TODO_PROPERTY_NAME);
+ }
+
+ @Order(2)
+ @Test
+ public void addAndRemoveProperty() {
+ // property removal is assured by RemovePropertyTest#testTodoPropertyIsMissing
+ configCommand.addToApplicationPropertiesFile(TODO_PROPERTY_NAME, "nice");
+ configCommand
+ .removeProperty()
+ .name(TODO_PROPERTY_NAME)
+ .executeCommand()
+ .assertCommandOutputContains("""
+ Removing configuration %s
+ """.formatted(TODO_PROPERTY_NAME))
+ .assertApplicationPropertiesDoesNotContain(TODO_PROPERTY_NAME);
+ }
+
+ @Order(3)
+ @Test
+ public void testRemoveCommandHelp() {
+ configCommand
+ .removeProperty()
+ .printOutHelp()
+ .assertCommandOutputContains("""
+ Removes a configuration from application.properties
+ """)
+ .assertCommandOutputContains("""
+ Usage: quarkus config remove [-eh] [--verbose] NAME
+ """)
+ .assertCommandOutputContains("""
+ Configuration name
+ """)
+ .assertCommandOutputContains("""
+ Print more context on errors and exceptions
+ """)
+ .assertCommandOutputContains("""
+ Display this help message
+ """)
+ .assertCommandOutputContains("""
+ Verbose mode
+ """);
+ }
+
+ @Order(4)
+ @Test
+ public void testQuarkusApplicationWithRemovedApplicationProperties() {
+ configCommand.buildAppAndExpectSuccess(RemovePropertyTest.class);
+ }
+
+}
diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigSetIT.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigSetIT.java
new file mode 100644
index 000000000..4b27625f3
--- /dev/null
+++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigSetIT.java
@@ -0,0 +1,182 @@
+package io.quarkus.ts.quarkus.cli;
+
+import static io.quarkus.test.bootstrap.config.QuarkusEncryptConfigCommandBuilder.AES_GCM_NO_PADDING_HANDLER_ENC_KEY;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.CREATE_1;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.CREATE_2;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.CREATE_3;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.UPDATE_1;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.UPDATE_2;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import io.quarkus.test.bootstrap.config.QuarkusConfigCommand;
+import io.quarkus.test.bootstrap.config.QuarkusSetConfigCommandBuilder.EncryptOption;
+import io.quarkus.test.scenarios.QuarkusScenario;
+import io.quarkus.test.scenarios.annotations.DisabledOnNative;
+import io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class) // remember, this is stateful test as well as stateful cmd builder
+@Tag("QUARKUS-3456")
+@Tag("quarkus-cli")
+@QuarkusScenario
+@DisabledOnNative // Only for JVM verification
+public class QuarkusCliConfigSetIT {
+
+ @Inject
+ static QuarkusConfigCommand configCommand;
+
+ @BeforeAll
+ public static void beforeAll() {
+ configCommand.withSmallRyeConfigCryptoDep();
+ }
+
+ @Order(1)
+ @Test
+ public void createPropertyCommand_NoSecret_ApplicationPropertiesDoesNotExist() {
+ // configured props tested by SetPropertyTest#createPropertyCommand_NoSecret_ApplicationPropertiesDoesNotExist
+ configCommand.removeApplicationProperties();
+ configCommand
+ .createProperty()
+ .name(CREATE_1.propertyName)
+ .value(CREATE_1.propertyValue)
+ .executeCommand()
+ .assertCommandOutputContains("""
+ Could not find an application.properties file, creating one now
+ """)
+ .assertCommandOutputContains("""
+ application.properties file created in src/main/resources
+ """)
+ .assertCommandOutputContains("""
+ Adding configuration %s with value %s
+ """.formatted(CREATE_1.propertyName, CREATE_1.propertyValue))
+ .assertApplicationPropertiesContains(CREATE_1.propertyName, CREATE_1.propertyValue);
+ }
+
+ @Order(2)
+ @Test
+ public void createPropertyCommand_NoSecret_ApplicationPropertiesExists() {
+ // configured props tested by SetPropertyTest#createPropertyCommand_NoSecret_ApplicationPropertiesExists
+ configCommand
+ .createProperty()
+ .name(CREATE_2.propertyName)
+ .value(CREATE_2.propertyValue)
+ .executeCommand()
+ .assertCommandOutputContains("""
+ Adding configuration %s with value %s
+ """.formatted(CREATE_2.propertyName, CREATE_2.propertyValue))
+ .assertCommandOutputNotContains("""
+ Could not find an application.properties file, creating one now
+ """)
+ .assertCommandOutputNotContains("""
+ application.properties file created in src/main/resources
+ """)
+ .assertApplicationPropertiesContains(CREATE_2.propertyName, CREATE_2.propertyValue);
+ }
+
+ @Order(3)
+ @Test
+ public void createPropertyCommand_EncryptValue_UseExistingEncryptionKey() {
+ // configured props tested by SetPropertyTest#createPropertyCommand_EncryptValue_UseExistingEncryptionKey
+ // use existing secret
+ configCommand.addToApplicationPropertiesFile(AES_GCM_NO_PADDING_HANDLER_ENC_KEY, "čingischán%ˇáíé=");
+
+ configCommand
+ .createProperty()
+ .name(CREATE_3.propertyName)
+ .value(CREATE_3.propertyValue)
+ .encrypt(EncryptOption.LONG)
+ .executeCommand()
+ .assertCommandOutputContains("""
+ Adding configuration %s with value ${aes-gcm-nopadding::
+ """.formatted(CREATE_3.propertyName))
+ .assertCommandOutputNotContains(CREATE_3.propertyValue)
+ .assertApplicationPropertiesContains(CREATE_3.propertyName + "=")
+ .assertApplicationPropertiesDoesNotContain(CREATE_3.propertyValue);
+ }
+
+ @Order(4)
+ @Test
+ public void updatePropertyCommand_ReplaceOriginalValueWithNewValue_EncryptNewValue() {
+ // configured props tested by SetPropertyTest#updatePropertyCommand_ReplaceOriginalValueWithNewValue_EncryptNewValue
+ var wrongValue = "Wrong value! Danger Will Robinson!";
+ configCommand.addToApplicationPropertiesFile(UPDATE_1.propertyName, wrongValue);
+
+ configCommand
+ .updateProperty()
+ .name(UPDATE_1.propertyName)
+ .value(UPDATE_1.propertyValue)
+ .encrypt(EncryptOption.SHORT)
+ .executeCommand()
+ .assertCommandOutputContains("""
+ Setting configuration %s to value ${aes-gcm-nopadding::
+ """.formatted(UPDATE_1.propertyName))
+ .assertCommandOutputNotContains(UPDATE_1.propertyValue)
+ .assertCommandOutputNotContains(wrongValue)
+ .assertApplicationPropertiesDoesNotContain(UPDATE_1.propertyValue)
+ .assertApplicationPropertiesDoesNotContain(wrongValue);
+ }
+
+ @Order(5)
+ @Test
+ public void updatePropertyCommand_EncryptOriginalValue() {
+ // configured props tested by SetPropertyTest#updatePropertyCommand_EncryptOriginalValue
+ configCommand.addToApplicationPropertiesFile(UPDATE_2.propertyName, UPDATE_2.propertyValue);
+
+ configCommand
+ .updateProperty()
+ .name(UPDATE_2.propertyName)
+ .encrypt(EncryptOption.LONG)
+ .executeCommand()
+ .assertCommandOutputContains("""
+ Setting configuration %s to value ${aes-gcm-nopadding::
+ """.formatted(UPDATE_2.propertyName))
+ .assertCommandOutputNotContains(UPDATE_2.propertyValue)
+ .assertApplicationPropertiesDoesNotContain(UPDATE_2.propertyValue);
+ }
+
+ @Order(6)
+ @Test
+ public void testSetCommandHelp() {
+ configCommand
+ .setProperty()
+ .printOutHelp()
+ .assertCommandOutputContains("""
+ Sets a configuration in application.properties
+ """)
+ .assertCommandOutputContains("""
+ Usage: quarkus config set [-ehk] [--verbose] NAME [VALUE]
+ """)
+ .assertCommandOutputContains("""
+ Configuration name
+ """)
+ .assertCommandOutputContains("""
+ Configuration value
+ """)
+ .assertCommandOutputContains("""
+ Print more context on errors and exceptions
+ """)
+ .assertCommandOutputContains("""
+ Display this help message
+ """)
+ .assertCommandOutputContains("""
+ Verbose mode
+ """)
+ .assertCommandOutputContains("""
+ Encrypt the configuration value
+ """)
+ .assertCommandOutputContains("-k, --encrypt");
+ }
+
+ @Order(8)
+ @Test
+ public void testQuarkusApplicationWithModifiedApplicationProperties() {
+ configCommand.buildAppAndExpectSuccess(SetPropertyTest.class);
+ }
+}
diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliHelpIT.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliHelpIT.java
index 19e82bf5a..43d8a534d 100644
--- a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliHelpIT.java
+++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliHelpIT.java
@@ -36,6 +36,7 @@ enum CommandWithHelp {
CREATE("create", "Create a new project."),
CREATE_CLI("create cli", "Create a Quarkus command-line project."),
CREATE_APP("create app", "Create a Quarkus application project."),
+ CONFIG("config", "Manage Quarkus configuration"),
BUILD("build", "Build the current project."),
DEV("dev", "Run the current project in dev (live coding) mode."),
EXTENSION("extension", "List platforms and extensions."),
diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/EncryptPropertyTest.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/EncryptPropertyTest.java
new file mode 100644
index 000000000..18aa8aac2
--- /dev/null
+++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/EncryptPropertyTest.java
@@ -0,0 +1,115 @@
+package io.quarkus.ts.quarkus.cli.config.surefire;
+
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_1;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_2;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_3;
+import static io.quarkus.ts.quarkus.cli.config.surefire.EncryptPropertyTest.EncryptProperties.SECRET_4;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.NoSuchElementException;
+
+import jakarta.inject.Inject;
+
+import org.eclipse.microprofile.config.Config;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.OS;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.smallrye.config.SmallRyeConfig;
+import io.smallrye.config.SmallRyeConfigBuilder;
+
+/**
+ * This test is only support to run inside QuarkusCliConfigEncryptIT.
+ */
+@QuarkusTest
+public class EncryptPropertyTest {
+
+ public static final String ENCRYPTED_SECRET_3_PROPERTY = "encrypted-secret-3";
+ public static final String UNKNOWN_SECRET_HANDLER_PROPERTY = "unknown-secret-handler";
+
+ @Inject
+ SmallRyeConfig config;
+
+ @Test
+ void encryptedSecret_Base64SecretFormat_GeneratedEncryptionKey() {
+ assertEquals(SECRET_1.secret, getSecret1());
+ }
+
+ @Test
+ public void encryptSecret_PlainKeyFormat_ExistingEncryptionKey() {
+ assertEquals(SECRET_2.secret, getSecret2());
+ }
+
+ @Test
+ public void failToDecryptSecretInWrongFormat() {
+ // wrong encryption key
+ assertThrows(RuntimeException.class, () -> getSecret3(config));
+ // right encryption key
+ var configWithRightKey = new SmallRyeConfigBuilder()
+ .addDefaultInterceptors()
+ .addDiscoveredSecretKeysHandlers()
+ .withDefaultValue("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key",
+ encode(SECRET_3.encryptionKey))
+ .withDefaultValue(SECRET_3.propertyName,
+ "${aes-gcm-nopadding::%s}".formatted(config.getConfigValue(ENCRYPTED_SECRET_3_PROPERTY).getValue()))
+ .build();
+ assertEquals(SECRET_3.secret, getSecret3(configWithRightKey));
+ }
+
+ @Test
+ public void testWrongSecretKeyHandler() {
+ assertThrows(NoSuchElementException.class, this::getSecretFromUnknownSecretHandler);
+ }
+
+ @Test
+ public void testKeyStoreConfigSourceWithSecrets() {
+ Assumptions.assumeFalse(OS.WINDOWS.isCurrentOs()); // Keytool command would require adjustments on Windows
+
+ assertEquals(SECRET_4.secret, getSecret4());
+ }
+
+ private void getSecretFromUnknownSecretHandler() {
+ config.getValue(UNKNOWN_SECRET_HANDLER_PROPERTY, String.class);
+ }
+
+ private String getSecret1() {
+ return config.getValue(SECRET_1.propertyName, String.class);
+ }
+
+ private String getSecret2() {
+ return config.getValue(SECRET_2.propertyName, String.class);
+ }
+
+ private static String getSecret3(Config config) {
+ return config.getValue(SECRET_3.propertyName, String.class);
+ }
+
+ private String getSecret4() {
+ return config.getValue(SECRET_4.propertyName, String.class);
+ }
+
+ private static String encode(String key) {
+ return Base64.getUrlEncoder().withoutPadding().encodeToString((key.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ public enum EncryptProperties {
+ SECRET_1("secret-1", "!@#$^%^&*()__++_)--=", null),
+ SECRET_2("secret-2", "charter school", null),
+ SECRET_3("secret-3", "Jr Gong", "Make It Bun Dem"),
+ SECRET_4("secret-4", "Joe Biden", null);
+
+ public final String propertyName;
+ public final String secret;
+ public final String encryptionKey;
+
+ EncryptProperties(String propertyName, String secret, String encryptionKey) {
+ this.propertyName = propertyName;
+ this.secret = secret;
+ this.encryptionKey = encryptionKey;
+ }
+ }
+}
diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/RemovePropertyTest.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/RemovePropertyTest.java
new file mode 100644
index 000000000..f18971519
--- /dev/null
+++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/RemovePropertyTest.java
@@ -0,0 +1,28 @@
+package io.quarkus.ts.quarkus.cli.config.surefire;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import jakarta.inject.Inject;
+
+import org.eclipse.microprofile.config.Config;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+/**
+ * This test is only supposed to run inside QuarkusCliConfigRemoveIT.
+ */
+@QuarkusTest
+public class RemovePropertyTest {
+
+ public static String TODO_PROPERTY_NAME = "todo";
+
+ @Inject
+ Config config;
+
+ @Test
+ void testTodoPropertyIsMissing() {
+ assertFalse(config.getOptionalValue(TODO_PROPERTY_NAME, String.class).isPresent());
+ }
+
+}
diff --git a/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/SetPropertyTest.java b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/SetPropertyTest.java
new file mode 100644
index 000000000..3001ca983
--- /dev/null
+++ b/quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/SetPropertyTest.java
@@ -0,0 +1,66 @@
+package io.quarkus.ts.quarkus.cli.config.surefire;
+
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.CREATE_1;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.CREATE_2;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.CREATE_3;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.UPDATE_1;
+import static io.quarkus.ts.quarkus.cli.config.surefire.SetPropertyTest.Properties.UPDATE_2;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.smallrye.config.SmallRyeConfig;
+
+/**
+ * This test is only support to run inside QuarkusCliConfigSetIT.
+ */
+@QuarkusTest
+public class SetPropertyTest {
+
+ @Inject
+ SmallRyeConfig config;
+
+ @Test
+ void createPropertyCommand_NoSecret_ApplicationPropertiesDoesNotExist() {
+ assertEquals(CREATE_1.propertyValue, config.getRawValue(CREATE_1.propertyName));
+ }
+
+ @Test
+ void createPropertyCommand_NoSecret_ApplicationPropertiesExists() {
+ assertEquals(CREATE_2.propertyValue, config.getRawValue(CREATE_2.propertyName));
+ }
+
+ @Test
+ void createPropertyCommand_EncryptValue_UseExistingEncryptionKey() {
+ assertEquals(CREATE_3.propertyValue, config.getRawValue(CREATE_3.propertyName));
+ }
+
+ @Test
+ void updatePropertyCommand_ReplaceOriginalValueWithNewValue_EncryptNewValue() {
+ assertEquals(UPDATE_1.propertyValue, config.getRawValue(UPDATE_1.propertyName));
+ }
+
+ @Test
+ void updatePropertyCommand_EncryptOriginalValue() {
+ assertEquals(UPDATE_2.propertyValue, config.getRawValue(UPDATE_2.propertyName));
+ }
+
+ public enum Properties {
+ CREATE_1("create-one-key", "create-one-value"),
+ CREATE_2("create-two-key", "create-two-value"),
+ CREATE_3("create-three-key", "create-three-value"),
+ UPDATE_1("update-one-key", "update-one-value"),
+ UPDATE_2("update-two-key", "update-two-value");
+
+ public final String propertyName;
+ public final String propertyValue;
+
+ Properties(String propertyName, String propertyValue) {
+ this.propertyName = propertyName;
+ this.propertyValue = propertyValue;
+ }
+ }
+}