From b4cc150d06c27e87970c18be33fb9a1d49f789a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 24 Jul 2024 22:56:57 +0200 Subject: [PATCH] Test Quarkus CLI Config subcommands incl. encryption --- quarkus-cli/pom.xml | 16 ++ .../cli/QuarkusCliConfigEncryptIT.java | 207 ++++++++++++++++++ .../quarkus/cli/QuarkusCliConfigRemoveIT.java | 88 ++++++++ .../ts/quarkus/cli/QuarkusCliConfigSetIT.java | 182 +++++++++++++++ .../ts/quarkus/cli/QuarkusCliHelpIT.java | 1 + .../config/surefire/EncryptPropertyTest.java | 115 ++++++++++ .../config/surefire/RemovePropertyTest.java | 28 +++ .../cli/config/surefire/SetPropertyTest.java | 66 ++++++ 8 files changed, 703 insertions(+) create mode 100644 quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigEncryptIT.java create mode 100644 quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigRemoveIT.java create mode 100644 quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/QuarkusCliConfigSetIT.java create mode 100644 quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/EncryptPropertyTest.java create mode 100644 quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/RemovePropertyTest.java create mode 100644 quarkus-cli/src/test/java/io/quarkus/ts/quarkus/cli/config/surefire/SetPropertyTest.java 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; + } + } +}