diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5b8881133..1dc11851ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,6 +94,24 @@ Normal Gradle test filtering works, so a single dev-server-backed test can be ru Java 11 must be available to Gradle for these commands. +To run an SDK test against an externally managed server using the standard Temporal client +environment configuration, set `TEMPORAL_TEST_ENV_CONFIG_SERVER`. For example, the following runs +one Cloud-safe workflow test: + +```bash +TEMPORAL_TEST_ENV_CONFIG_SERVER=true \ +TEMPORAL_ADDRESS=your-namespace.tmprl.cloud:7233 \ +TEMPORAL_NAMESPACE=your-namespace \ +TEMPORAL_API_KEY=your-api-key \ +./gradlew :temporal-sdk:test \ + --tests 'io.temporal.client.functional.SignalTest.signalCompletedWorkflow' +``` + +The harness also supports the standard `TEMPORAL_CONFIG_FILE` and `TEMPORAL_PROFILE` variables. +Values from `TEMPORAL_ADDRESS`, `TEMPORAL_NAMESPACE`, `TEMPORAL_API_KEY`, `TEMPORAL_TLS_*`, and +`TEMPORAL_GRPC_META_*` override the selected profile. Envconfig mode connects to an existing server +and namespace; it does not create or register either one. + ## Things to Avoid Avoid changes that make review harder without improving the contribution: diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle index d7b090e5b5..594a7660e0 100644 --- a/temporal-sdk/build.gradle +++ b/temporal-sdk/build.gradle @@ -25,6 +25,7 @@ dependencies { } testImplementation project(':temporal-testing') + testRuntimeOnly project(':temporal-envconfig') testImplementation "junit:junit:${junitVersion}" testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation 'pl.pragmatists:JUnitParams:1.1.1' @@ -287,4 +288,4 @@ testing { tasks.named('check') { dependsOn(testing.suites.jackson3Tests) dependsOn(testing.suites.virtualThreadTests) -} \ No newline at end of file +} diff --git a/temporal-testing/build.gradle b/temporal-testing/build.gradle index f9ca013456..10a7ddc6a1 100644 --- a/temporal-testing/build.gradle +++ b/temporal-testing/build.gradle @@ -16,6 +16,7 @@ java { dependencies { api project(':temporal-sdk') api project(':temporal-test-server') + compileOnly project(':temporal-envconfig') implementation 'org.apache.commons:commons-compress:1.28.0' @@ -32,6 +33,7 @@ dependencies { junit5Api 'org.junit.jupiter:junit-jupiter-api' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter' + testRuntimeOnly project(':temporal-envconfig') testRuntimeOnly group: 'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}" } diff --git a/temporal-testing/src/main/java/io/temporal/internal/docker/RegisterTestNamespace.java b/temporal-testing/src/main/java/io/temporal/internal/docker/RegisterTestNamespace.java index c840adf55c..b43a9ca3cf 100644 --- a/temporal-testing/src/main/java/io/temporal/internal/docker/RegisterTestNamespace.java +++ b/temporal-testing/src/main/java/io/temporal/internal/docker/RegisterTestNamespace.java @@ -7,6 +7,7 @@ import io.temporal.api.workflowservice.v1.ListNamespacesRequest; import io.temporal.api.workflowservice.v1.ListNamespacesResponse; import io.temporal.api.workflowservice.v1.RegisterNamespaceRequest; +import io.temporal.internal.common.env.EnvironmentVariableUtils; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.serviceclient.WorkflowServiceStubsOptions; @@ -15,10 +16,12 @@ public class RegisterTestNamespace { public static final String NAMESPACE = "UnitTest"; private static final boolean useExternalService = Boolean.parseBoolean(System.getenv("USE_EXTERNAL_SERVICE")); + private static final boolean useEnvConfig = + EnvironmentVariableUtils.readBooleanFlag("TEMPORAL_TEST_ENV_CONFIG_SERVER"); private static final String serviceAddress = System.getenv("TEMPORAL_SERVICE_ADDRESS"); public static void main(String[] args) throws InterruptedException { - if (!useExternalService) { + if (!useExternalService || useEnvConfig) { return; } diff --git a/temporal-testing/src/main/java/io/temporal/testing/internal/ExternalServiceTestConfigurator.java b/temporal-testing/src/main/java/io/temporal/testing/internal/ExternalServiceTestConfigurator.java index 6c68d5f2b5..95c213f219 100644 --- a/temporal-testing/src/main/java/io/temporal/testing/internal/ExternalServiceTestConfigurator.java +++ b/temporal-testing/src/main/java/io/temporal/testing/internal/ExternalServiceTestConfigurator.java @@ -1,43 +1,94 @@ package io.temporal.testing.internal; -import io.temporal.internal.common.env.EnvironmentVariableUtils; +import io.grpc.Metadata; +import io.temporal.client.ActivityClientOptions; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.envconfig.LoadClientConfigProfileOptions; +import io.temporal.serviceclient.GrpcMetadataProvider; +import io.temporal.serviceclient.WorkflowServiceStubsOptions; import io.temporal.testing.TestEnvironmentOptions; import io.temporal.testing.TestWorkflowRule; import io.temporal.testing.internal.devserver.SdkJavaTestServerProfile; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import javax.annotation.Nonnull; public class ExternalServiceTestConfigurator { - private static boolean USE_EXTERNAL_SERVICE = - EnvironmentVariableUtils.readBooleanFlag("USE_EXTERNAL_SERVICE"); - private static String TEMPORAL_SERVICE_ADDRESS = - EnvironmentVariableUtils.readString("TEMPORAL_SERVICE_ADDRESS"); - private static boolean USE_VIRTUAL_THREADS = - EnvironmentVariableUtils.readBooleanFlag("USE_VIRTUAL_THREADS"); + static final String TEMPORAL_TEST_ENV_CONFIG_SERVER = "TEMPORAL_TEST_ENV_CONFIG_SERVER"; + private static final String USE_EXTERNAL_SERVICE = "USE_EXTERNAL_SERVICE"; + private static final String TEMPORAL_SERVICE_ADDRESS = "TEMPORAL_SERVICE_ADDRESS"; + private static final String USE_VIRTUAL_THREADS = "USE_VIRTUAL_THREADS"; public static boolean isUseExternalService() { - return USE_EXTERNAL_SERVICE || SdkJavaTestServerProfile.isActive(); + return isUseExternalService(System.getenv()) || SdkJavaTestServerProfile.isActive(); } public static boolean isUseVirtualThreads() { - return USE_VIRTUAL_THREADS; + return readBooleanFlag(System.getenv(), USE_VIRTUAL_THREADS); } public static String getTemporalServiceAddress() { - if (SdkJavaTestServerProfile.isActive()) { - return SdkJavaTestServerProfile.getTarget(); + Map environment = System.getenv(); + if (readBooleanFlag(environment, TEMPORAL_TEST_ENV_CONFIG_SERVER)) { + return getTemporalServiceAddress(environment); } - return USE_EXTERNAL_SERVICE - ? (TEMPORAL_SERVICE_ADDRESS != null ? TEMPORAL_SERVICE_ADDRESS : "127.0.0.1:7233") - : null; + String devServerTarget = SdkJavaTestServerProfile.getTarget(); + if (devServerTarget != null) { + return devServerTarget; + } + return getTemporalServiceAddress(environment); } public static TestWorkflowRule.Builder configure( @Nonnull TestWorkflowRule.Builder testWorkflowRule) { - if (isUseExternalService()) { + return configure(testWorkflowRule, System.getenv(), SdkJavaTestServerProfile.getTarget()); + } + + static TestWorkflowRule.Builder configure( + TestWorkflowRule.Builder testWorkflowRule, Map environment) { + return configure(testWorkflowRule, environment, null); + } + + static TestWorkflowRule.Builder configure( + TestWorkflowRule.Builder testWorkflowRule, + Map environment, + String devServerTarget) { + return configure(testWorkflowRule, environment, devServerTarget, true); + } + + static TestWorkflowRule.Builder configureConnection( + TestWorkflowRule.Builder testWorkflowRule, + Map environment, + String devServerTarget) { + return configure(testWorkflowRule, environment, devServerTarget, false); + } + + private static TestWorkflowRule.Builder configure( + TestWorkflowRule.Builder testWorkflowRule, + Map environment, + String devServerTarget, + boolean configureOptions) { + ClientConfigProfile profile = loadEnvConfigProfile(environment); + if (profile != null) { + testWorkflowRule.setUseExternalService(true); + testWorkflowRule.setTarget(profile.getAddress()); + testWorkflowRule.setNamespace(profile.getNamespace()); + if (configureOptions) { + testWorkflowRule.setWorkflowServiceStubsOptions(profile.toWorkflowServiceStubsOptions()); + testWorkflowRule.setWorkflowClientOptions(profile.toWorkflowClientOptions()); + } + } else if (devServerTarget != null) { + testWorkflowRule.setUseExternalService(true); + testWorkflowRule.setTarget(devServerTarget); + } else if (readBooleanFlag(environment, USE_EXTERNAL_SERVICE)) { testWorkflowRule.setUseExternalService(true); - String target = getTemporalServiceAddress(); - if (target != null) { - testWorkflowRule.setTarget(target); + String serviceAddress = environment.get(TEMPORAL_SERVICE_ADDRESS); + if (serviceAddress != null) { + testWorkflowRule.setTarget(serviceAddress); } } return testWorkflowRule; @@ -45,11 +96,33 @@ public static TestWorkflowRule.Builder configure( public static TestEnvironmentOptions.Builder configure( @Nonnull TestEnvironmentOptions.Builder testEnvironmentOptions) { - if (isUseExternalService()) { + return configure(testEnvironmentOptions, System.getenv(), SdkJavaTestServerProfile.getTarget()); + } + + static TestEnvironmentOptions.Builder configure( + TestEnvironmentOptions.Builder testEnvironmentOptions, Map environment) { + return configure(testEnvironmentOptions, environment, null); + } + + static TestEnvironmentOptions.Builder configure( + TestEnvironmentOptions.Builder testEnvironmentOptions, + Map environment, + String devServerTarget) { + ClientConfigProfile profile = loadEnvConfigProfile(environment); + if (profile != null) { + testEnvironmentOptions.setUseExternalService(true); + testEnvironmentOptions.setTarget(profile.getAddress()); + testEnvironmentOptions.setWorkflowServiceStubsOptions( + profile.toWorkflowServiceStubsOptions()); + testEnvironmentOptions.setWorkflowClientOptions(profile.toWorkflowClientOptions()); + } else if (devServerTarget != null) { + testEnvironmentOptions.setUseExternalService(true); + testEnvironmentOptions.setTarget(devServerTarget); + } else if (readBooleanFlag(environment, USE_EXTERNAL_SERVICE)) { testEnvironmentOptions.setUseExternalService(true); - String target = getTemporalServiceAddress(); - if (target != null) { - testEnvironmentOptions.setTarget(target); + String serviceAddress = environment.get(TEMPORAL_SERVICE_ADDRESS); + if (serviceAddress != null) { + testEnvironmentOptions.setTarget(serviceAddress); } } return testEnvironmentOptions; @@ -58,4 +131,132 @@ public static TestEnvironmentOptions.Builder configure( public static TestEnvironmentOptions.Builder configuredTestEnvironmentOptions() { return configure(TestEnvironmentOptions.newBuilder()); } + + static WorkflowServiceStubsOptions configure( + WorkflowServiceStubsOptions workflowServiceStubsOptions, Map environment) { + ClientConfigProfile profile = loadEnvConfigProfile(environment); + if (profile == null) { + return workflowServiceStubsOptions; + } + + WorkflowServiceStubsOptions profileOptions = profile.toWorkflowServiceStubsOptions(); + GrpcMetadataProvider metadataProvider = + mergeMetadata( + profileOptions.getHeaders(), + profileOptions.getGrpcMetadataProviders(), + workflowServiceStubsOptions.getHeaders(), + workflowServiceStubsOptions.getGrpcMetadataProviders()); + return WorkflowServiceStubsOptions.newBuilder(workflowServiceStubsOptions) + .setChannel(null) + .setTarget(profileOptions.getTarget()) + .setEnableHttps(profileOptions.getEnableHttps()) + .setSslContext(profileOptions.getSslContext()) + .setChannelInitializer(profileOptions.getChannelInitializer()) + .setHeaders(new Metadata()) + .setGrpcMetadataProviders(Collections.singletonList(metadataProvider)) + .build(); + } + + private static GrpcMetadataProvider mergeMetadata( + Metadata profileHeaders, + Iterable profileProviders, + Metadata testHeaders, + Iterable testProviders) { + List profileProviderList = new ArrayList<>(); + if (profileProviders != null) { + profileProviders.forEach(profileProviderList::add); + } + return () -> { + Metadata metadata = new Metadata(); + if (testHeaders != null) { + metadata.merge(testHeaders); + } + if (testProviders != null) { + testProviders.forEach(provider -> metadata.merge(provider.getMetadata())); + } + Metadata profileMetadata = new Metadata(); + if (profileHeaders != null) { + profileMetadata.merge(profileHeaders); + } + profileProviderList.forEach(provider -> profileMetadata.merge(provider.getMetadata())); + for (String keyName : profileMetadata.keys()) { + if (keyName.endsWith("-bin")) { + metadata.discardAll(Metadata.Key.of(keyName, Metadata.BINARY_BYTE_MARSHALLER)); + } else { + metadata.discardAll(Metadata.Key.of(keyName, Metadata.ASCII_STRING_MARSHALLER)); + } + } + metadata.merge(profileMetadata); + return metadata; + }; + } + + static WorkflowClientOptions configure( + WorkflowClientOptions workflowClientOptions, Map environment) { + ClientConfigProfile profile = loadEnvConfigProfile(environment); + return profile == null + ? workflowClientOptions + : WorkflowClientOptions.newBuilder(workflowClientOptions) + .setNamespace(profile.getNamespace()) + .build(); + } + + static ActivityClientOptions configure( + ActivityClientOptions activityClientOptions, Map environment) { + ClientConfigProfile profile = loadEnvConfigProfile(environment); + return profile == null + ? activityClientOptions + : ActivityClientOptions.newBuilder(activityClientOptions) + .setNamespace(profile.getNamespace()) + .build(); + } + + static boolean isUseExternalService(Map environment) { + return readBooleanFlag(environment, TEMPORAL_TEST_ENV_CONFIG_SERVER) + || readBooleanFlag(environment, USE_EXTERNAL_SERVICE); + } + + static String getTemporalServiceAddress(Map environment) { + ClientConfigProfile profile = loadEnvConfigProfile(environment); + if (profile != null) { + return profile.getAddress(); + } + return readBooleanFlag(environment, USE_EXTERNAL_SERVICE) + ? (environment.get(TEMPORAL_SERVICE_ADDRESS) != null + ? environment.get(TEMPORAL_SERVICE_ADDRESS) + : "127.0.0.1:7233") + : null; + } + + private static ClientConfigProfile loadEnvConfigProfile(Map environment) { + if (!readBooleanFlag(environment, TEMPORAL_TEST_ENV_CONFIG_SERVER)) { + return null; + } + + ClientConfigProfile profile; + try { + profile = + ClientConfigProfile.load( + LoadClientConfigProfileOptions.newBuilder().setEnvOverrides(environment).build()); + } catch (IOException e) { + throw new IllegalStateException( + "Unable to load client configuration for the Temporal test harness.", e); + } + if (profile.getAddress() == null || profile.getAddress().isEmpty()) { + throw new IllegalStateException("Envconfig test harness requires a Temporal server address."); + } + if (profile.getNamespace() == null || profile.getNamespace().isEmpty()) { + throw new IllegalStateException("Envconfig test harness requires a Temporal namespace."); + } + return profile; + } + + private static boolean readBooleanFlag(Map environment, String variableName) { + String value = environment.get(variableName); + if (value == null) { + return false; + } + value = value.trim(); + return !Boolean.FALSE.toString().equalsIgnoreCase(value) && !"0".equals(value); + } } diff --git a/temporal-testing/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java b/temporal-testing/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java index 9c81461212..c6109c5ec5 100644 --- a/temporal-testing/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java +++ b/temporal-testing/src/main/java/io/temporal/testing/internal/SDKTestWorkflowRule.java @@ -31,6 +31,7 @@ import io.temporal.serviceclient.WorkflowServiceStubsOptions; import io.temporal.testing.TestWorkflowEnvironment; import io.temporal.testing.TestWorkflowRule; +import io.temporal.testing.internal.devserver.SdkJavaTestServerProfile; import io.temporal.worker.*; import io.temporal.workflow.Functions; import java.io.File; @@ -40,6 +41,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.*; import javax.annotation.Nonnull; @@ -91,7 +93,9 @@ private SDKTestWorkflowRule(SDKTestWorkflowRule.Builder builder) { : null; testWorkflowRule = - ExternalServiceTestConfigurator.configure(builder.testWorkflowRuleBuilder).build(); + ExternalServiceTestConfigurator.configureConnection( + builder.testWorkflowRuleBuilder, builder.environment, builder.devServerTarget) + .build(); } public static Builder newBuilder() { @@ -104,24 +108,43 @@ public static class Builder { private boolean workerFactoryOptionsAreSet = false; private boolean workerOptionsAreSet = false; private final TestWorkflowRule.Builder testWorkflowRuleBuilder; + private final Map environment; + @Nullable private final String devServerTarget; public Builder() { - testWorkflowRuleBuilder = TestWorkflowRule.newBuilder(); + this(System.getenv(), SdkJavaTestServerProfile.getTarget()); + } + + Builder(Map environment, @Nullable String devServerTarget) { + this.environment = environment; + this.devServerTarget = devServerTarget; + testWorkflowRuleBuilder = + ExternalServiceTestConfigurator.configure( + TestWorkflowRule.newBuilder(), environment, devServerTarget); } public Builder setWorkflowServiceStubsOptions( WorkflowServiceStubsOptions workflowServiceStubsOptions) { - testWorkflowRuleBuilder.setWorkflowServiceStubsOptions(workflowServiceStubsOptions); + if (workflowServiceStubsOptions != null) { + testWorkflowRuleBuilder.setWorkflowServiceStubsOptions( + ExternalServiceTestConfigurator.configure(workflowServiceStubsOptions, environment)); + } return this; } public Builder setWorkflowClientOptions(WorkflowClientOptions workflowClientOptions) { - testWorkflowRuleBuilder.setWorkflowClientOptions(workflowClientOptions); + if (workflowClientOptions != null) { + testWorkflowRuleBuilder.setWorkflowClientOptions( + ExternalServiceTestConfigurator.configure(workflowClientOptions, environment)); + } return this; } public Builder setActivityClientOptions(ActivityClientOptions activityClientOptions) { - testWorkflowRuleBuilder.setActivityClientOptions(activityClientOptions); + if (activityClientOptions != null) { + testWorkflowRuleBuilder.setActivityClientOptions( + ExternalServiceTestConfigurator.configure(activityClientOptions, environment)); + } return this; } diff --git a/temporal-testing/src/test/java/io/temporal/testing/internal/ExternalServiceTestConfiguratorTest.java b/temporal-testing/src/test/java/io/temporal/testing/internal/ExternalServiceTestConfiguratorTest.java new file mode 100644 index 0000000000..cd7a72114e --- /dev/null +++ b/temporal-testing/src/test/java/io/temporal/testing/internal/ExternalServiceTestConfiguratorTest.java @@ -0,0 +1,227 @@ +package io.temporal.testing.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.grpc.Metadata; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.serviceclient.WorkflowServiceStubsOptions; +import io.temporal.testing.TestEnvironmentOptions; +import io.temporal.testing.TestWorkflowRule; +import java.io.File; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +public class ExternalServiceTestConfiguratorTest { + + @Test + public void configureTestWorkflowRuleFromEnvConfig() { + Map environment = newEnvConfigEnvironment(); + environment.put("USE_EXTERNAL_SERVICE", "true"); + environment.put("TEMPORAL_SERVICE_ADDRESS", "legacy-address:7233"); + + TestWorkflowRule rule = + ExternalServiceTestConfigurator.configure(TestWorkflowRule.newBuilder(), environment) + .build(); + try { + assertEquals( + "envconfig-address:7233", rule.getWorkflowServiceStubs().getOptions().getTarget()); + assertEquals("envconfig-namespace", rule.getWorkflowClient().getOptions().getNamespace()); + + WorkflowServiceStubsOptions stubsOptions = rule.getWorkflowServiceStubs().getOptions(); + assertTrue(stubsOptions.getEnableHttps()); + Metadata metadata = new Metadata(); + stubsOptions + .getGrpcMetadataProviders() + .forEach(provider -> metadata.merge(provider.getMetadata())); + assertEquals( + "metadata-value", + metadata.get(Metadata.Key.of("test-header", Metadata.ASCII_STRING_MARSHALLER))); + assertEquals( + "Bearer api-key", + metadata.get(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER))); + } finally { + rule.getTestEnvironment().close(); + } + } + + @Test + public void preserveLegacyAndLocalModes() { + Map environment = new HashMap<>(); + TestEnvironmentOptions localOptions = + ExternalServiceTestConfigurator.configure(TestEnvironmentOptions.newBuilder(), environment) + .build(); + assertFalse(localOptions.isUseExternalService()); + + environment.put("USE_EXTERNAL_SERVICE", "true"); + environment.put("TEMPORAL_SERVICE_ADDRESS", "legacy-address:7233"); + TestEnvironmentOptions externalOptions = + ExternalServiceTestConfigurator.configure(TestEnvironmentOptions.newBuilder(), environment) + .build(); + assertTrue(externalOptions.isUseExternalService()); + assertEquals("legacy-address:7233", externalOptions.getTarget()); + } + + @Test + public void preserveDevServerProfilePrecedence() { + Map environment = new HashMap<>(); + environment.put("USE_EXTERNAL_SERVICE", "true"); + environment.put("TEMPORAL_SERVICE_ADDRESS", "legacy-address:7233"); + + TestEnvironmentOptions devServerOptions = + ExternalServiceTestConfigurator.configure( + TestEnvironmentOptions.newBuilder(), environment, "dev-server-address:7233") + .build(); + assertTrue(devServerOptions.isUseExternalService()); + assertEquals("dev-server-address:7233", devServerOptions.getTarget()); + + TestEnvironmentOptions envConfigOptions = + ExternalServiceTestConfigurator.configure( + TestEnvironmentOptions.newBuilder(), + newEnvConfigEnvironment(), + "dev-server-address:7233") + .build(); + assertEquals("envconfig-address:7233", envConfigOptions.getTarget()); + assertEquals("envconfig-namespace", envConfigOptions.getWorkflowClientOptions().getNamespace()); + } + + @Test + public void preserveTestOptionsWhenApplyingEnvConfigConnection() { + Map environment = newEnvConfigEnvironment(); + Metadata.Key customHeader = + Metadata.Key.of("custom-option-header", Metadata.ASCII_STRING_MARSHALLER); + Metadata.Key authorizationHeader = + Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER); + Metadata.Key profileHeader = + Metadata.Key.of("test-header", Metadata.ASCII_STRING_MARSHALLER); + Metadata.Key fixedHeader = + Metadata.Key.of("custom-fixed-header", Metadata.ASCII_STRING_MARSHALLER); + Metadata fixedHeaders = new Metadata(); + fixedHeaders.put(fixedHeader, "custom-fixed-value"); + fixedHeaders.put(authorizationHeader, "Bearer stale-fixed-api-key"); + fixedHeaders.put(profileHeader, "stale-fixed-metadata-value"); + WorkflowServiceStubsOptions serviceOptions = + WorkflowServiceStubsOptions.newBuilder() + .setRpcTimeout(Duration.ofSeconds(17)) + .setHeaders(fixedHeaders) + .addGrpcMetadataProvider( + () -> { + Metadata metadata = new Metadata(); + metadata.put(customHeader, "custom-option-value"); + metadata.put(authorizationHeader, "Bearer stale-api-key"); + metadata.put(profileHeader, "stale-metadata-value"); + return metadata; + }) + .build(); + + WorkflowServiceStubsOptions configuredServiceOptions = + ExternalServiceTestConfigurator.configure(serviceOptions, environment); + assertEquals("envconfig-address:7233", configuredServiceOptions.getTarget()); + assertEquals(Duration.ofSeconds(17), configuredServiceOptions.getRpcTimeout()); + assertTrue(configuredServiceOptions.getEnableHttps()); + Metadata metadata = new Metadata(); + configuredServiceOptions + .getGrpcMetadataProviders() + .forEach(provider -> metadata.merge(provider.getMetadata())); + assertEquals("custom-option-value", metadata.get(customHeader)); + assertEquals("custom-fixed-value", metadata.get(fixedHeader)); + assertEquals("metadata-value", metadata.get(profileHeader)); + assertEquals("Bearer api-key", metadata.get(authorizationHeader)); + int authorizationValues = 0; + for (String ignored : metadata.getAll(authorizationHeader)) { + authorizationValues++; + } + assertEquals(1, authorizationValues); + + WorkflowClientOptions clientOptions = + WorkflowClientOptions.newBuilder() + .setNamespace("test-option-namespace") + .setIdentity("test-option-identity") + .build(); + WorkflowClientOptions configuredClientOptions = + ExternalServiceTestConfigurator.configure(clientOptions, environment); + assertEquals("envconfig-namespace", configuredClientOptions.getNamespace()); + assertEquals("test-option-identity", configuredClientOptions.getIdentity()); + } + + @Test + public void preserveTestOptionsWhenEnvConfigHasNoMetadataProviders() { + Map environment = newEnvConfigEnvironment(); + environment.remove("TEMPORAL_API_KEY"); + environment.remove("TEMPORAL_GRPC_META_TEST_HEADER"); + WorkflowServiceStubsOptions serviceOptions = + WorkflowServiceStubsOptions.newBuilder().setRpcTimeout(Duration.ofSeconds(17)).build(); + + WorkflowServiceStubsOptions configuredServiceOptions = + ExternalServiceTestConfigurator.configure(serviceOptions, environment); + + assertEquals("envconfig-address:7233", configuredServiceOptions.getTarget()); + assertEquals(Duration.ofSeconds(17), configuredServiceOptions.getRpcTimeout()); + } + + @Test + public void sdkRuleReappliesEnvConfigConnectionAfterTestCustomization() { + Map environment = newEnvConfigEnvironment(); + SDKTestWorkflowRule rule = + new SDKTestWorkflowRule.Builder(environment, null) + .setUseExternalService(false) + .setTarget("test-option-address:7233") + .setNamespace("test-option-namespace") + .setWorkflowServiceStubsOptions( + WorkflowServiceStubsOptions.newBuilder() + .setRpcTimeout(Duration.ofSeconds(17)) + .build()) + .setWorkflowClientOptions( + WorkflowClientOptions.newBuilder() + .setNamespace("test-option-namespace") + .setIdentity("test-option-identity") + .build()) + .build(); + try { + assertEquals( + "envconfig-address:7233", rule.getWorkflowServiceStubs().getOptions().getTarget()); + assertEquals( + Duration.ofSeconds(17), rule.getWorkflowServiceStubs().getOptions().getRpcTimeout()); + assertEquals("envconfig-namespace", rule.getWorkflowClient().getOptions().getNamespace()); + assertEquals("test-option-identity", rule.getWorkflowClient().getOptions().getIdentity()); + } finally { + rule.getTestEnvironment().close(); + } + } + + @Test + public void requireAddressAndNamespaceInEnvConfigMode() { + Map environment = new HashMap<>(); + environment.put(ExternalServiceTestConfigurator.TEMPORAL_TEST_ENV_CONFIG_SERVER, "true"); + environment.put("TEMPORAL_CONFIG_FILE", nonExistentConfigFile()); + environment.put("TEMPORAL_ADDRESS", "envconfig-address:7233"); + + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> + ExternalServiceTestConfigurator.configure( + TestEnvironmentOptions.newBuilder(), environment)); + assertEquals("Envconfig test harness requires a Temporal namespace.", exception.getMessage()); + } + + private static Map newEnvConfigEnvironment() { + Map environment = new HashMap<>(); + environment.put(ExternalServiceTestConfigurator.TEMPORAL_TEST_ENV_CONFIG_SERVER, "true"); + environment.put("TEMPORAL_CONFIG_FILE", nonExistentConfigFile()); + environment.put("TEMPORAL_ADDRESS", "envconfig-address:7233"); + environment.put("TEMPORAL_NAMESPACE", "envconfig-namespace"); + environment.put("TEMPORAL_API_KEY", "api-key"); + environment.put("TEMPORAL_GRPC_META_TEST_HEADER", "metadata-value"); + return environment; + } + + private static String nonExistentConfigFile() { + return new File("build/non-existent-envconfig-" + UUID.randomUUID()).getAbsolutePath(); + } +}