diff --git a/azuremanaged/build.gradle b/azuremanaged/build.gradle index 71c58c9c..6ede2393 100644 --- a/azuremanaged/build.gradle +++ b/azuremanaged/build.gradle @@ -44,6 +44,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.0' testImplementation 'org.mockito:mockito-core:5.3.1' testImplementation 'org.mockito:mockito-junit-jupiter:5.3.1' + testImplementation "io.grpc:grpc-netty:${grpcVersion}" testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0' } @@ -61,6 +62,7 @@ compileTestJava { test { useJUnitPlatform() + include '**/*Test.class' } publishing { @@ -174,4 +176,5 @@ compileJava.dependsOn generateVersionInfo tasks.named('sourcesJar') { dependsOn generateVersionInfo -} \ No newline at end of file +} + diff --git a/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerClientOptions.java b/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerClientOptions.java index be56be71..0e6cd24b 100644 --- a/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerClientOptions.java +++ b/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerClientOptions.java @@ -227,7 +227,7 @@ public void start(ClientCall.Listener responseListener, Metadata headers) ); headers.put( - Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), + Metadata.Key.of("x-user-agent", Metadata.ASCII_STRING_MARSHALLER), DurableTaskUserAgentUtil.getUserAgent() ); diff --git a/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerWorkerOptions.java b/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerWorkerOptions.java index bebf4f3a..d0c34af3 100644 --- a/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerWorkerOptions.java +++ b/azuremanaged/src/main/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerWorkerOptions.java @@ -236,7 +236,7 @@ public void start(ClientCall.Listener responseListener, Metadata headers) ); headers.put( - Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER), + Metadata.Key.of("x-user-agent", Metadata.ASCII_STRING_MARSHALLER), DurableTaskUserAgentUtil.getUserAgent() ); diff --git a/azuremanaged/src/test/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerClientExtensionsUserAgentTest.java b/azuremanaged/src/test/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerClientExtensionsUserAgentTest.java new file mode 100644 index 00000000..24a2513c --- /dev/null +++ b/azuremanaged/src/test/java/com/microsoft/durabletask/azuremanaged/DurableTaskSchedulerClientExtensionsUserAgentTest.java @@ -0,0 +1,103 @@ +package com.microsoft.durabletask.azuremanaged; + +import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; +import com.microsoft.durabletask.NewOrchestrationInstanceOptions; +import io.grpc.*; +import io.grpc.stub.ServerCalls; +import io.grpc.stub.StreamObserver; +import io.grpc.netty.NettyServerBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +public class DurableTaskSchedulerClientExtensionsUserAgentTest { + private Server server; + private Channel channel; + private final AtomicReference capturedUserAgent = new AtomicReference<>(); + private static final String EXPECTED_USER_AGENT_PREFIX = "durabletask-java/"; + + // Dummy gRPC service definition + public static class DummyService implements io.grpc.BindableService { + @Override + public ServerServiceDefinition bindService() { + return ServerServiceDefinition.builder("TaskHubSidecarService") + .addMethod( + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName(MethodDescriptor.generateFullMethodName("TaskHubSidecarService", "StartInstance")) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(com.google.protobuf.Empty.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(com.google.protobuf.Empty.getDefaultInstance())) + .build(), + ServerCalls.asyncUnaryCall( + new ServerCalls.UnaryMethod() { + @Override + public void invoke(com.google.protobuf.Empty request, StreamObserver responseObserver) { + // Mock response for StartInstance + responseObserver.onNext(com.google.protobuf.Empty.getDefaultInstance()); + responseObserver.onCompleted(); + } + } + ) + ) + .build(); + } + } + + @BeforeEach + public void setUp() throws IOException { + // Use NettyServerBuilder to expose the server via HTTP + server = NettyServerBuilder.forPort(0) + .addService(new DummyService()) // Register DummyService + .intercept(new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + String userAgent = headers.get(Metadata.Key.of("x-user-agent", Metadata.ASCII_STRING_MARSHALLER)); + capturedUserAgent.set(userAgent); + return next.startCall(call, headers); + } + }) + .directExecutor() + .build() + .start(); + int port = server.getPort(); + String endpoint = "localhost:" + port; + DurableTaskSchedulerClientOptions options = new DurableTaskSchedulerClientOptions(); + options.setEndpointAddress(endpoint); + options.setTaskHubName("testHub"); + options.setAllowInsecureCredentials(true); // Netty is insecure for localhost + channel = options.createGrpcChannel(); + } + + @AfterEach + public void tearDown() { + if (server != null) server.shutdownNow(); + if (channel != null && channel instanceof ManagedChannel) { + ((ManagedChannel) channel).shutdownNow(); + } + } + + @Test + public void testUserAgentHeaderIsSet() { + DurableTaskGrpcClientBuilder builder = new DurableTaskGrpcClientBuilder(); + builder.grpcChannel(channel); + DurableTaskClient client = builder.build(); + + // Schedule a new orchestration instance + String instanceId = client.scheduleNewOrchestrationInstance( + "TestOrchestration", + new NewOrchestrationInstanceOptions().setInput("TestInput")); + + // Make a dummy call to trigger the request + String userAgent = capturedUserAgent.get(); + assertNotNull(userAgent, "X-User-Agent header should be set"); + assertTrue(userAgent.startsWith(EXPECTED_USER_AGENT_PREFIX), "X-User-Agent should start with durabletask-java/"); + } +} +