public async Task UnhandledOrchestrationExceptionWithRetry() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.OrchestratorThrowWithRetry) }; using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(UnhandledOrchestrationExceptionWithRetry))) { await host.StartAsync(); // Null input should result in ArgumentNullException in the orchestration code. var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], null, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(50), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); Assert.True(status?.Output.ToString().Contains("Value cannot be null")); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.UnhandledOrchesterationExceptionWithRetry_AssertLogMessageSequence(loggerProvider, "UnhandledOrchestrationExceptionWithRetry", orchestratorFunctionNames); } }
public async Task BigReturnValue_Activity() { string taskHub = nameof(BigReturnValue_Activity); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, taskHub)) { await host.StartAsync(); var orchestrator = nameof(TestOrchestrations.CallActivity); var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); // The expected maximum payload size is 60 KB. // Strings in Azure Storage are encoded in UTF-32, which is 4 bytes per character. int stringLength = (61 * 1024) / 4; var input = new StartOrchestrationArgs { FunctionName = nameof(TestActivities.BigReturnValue), Input = stringLength }; var client = await host.StartOrchestratorAsync(orchestrator, input, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); // Activity function exception details are not captured in the orchestrator output: // https://github.com/Azure/azure-functions-durable-extension/issues/84 ////Assert.True(status?.Output.ToString().Contains("The UTF-32 size of the JSON-serialized payload must not exceed 60 KB")); Assert.StartsWith($"The activity function '{input.FunctionName}' failed.", (string)status?.Output); await host.StopAsync(); } }
public async Task SequentialOrchestration() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(SequentialOrchestration))) { await host.StartAsync(); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Factorial), 10, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal(10, status?.Input); Assert.Equal(3628800, status?.Output); await host.StopAsync(); } // Assert log entry count if (this.useTestLogger) { var logger = loggerProvider.CreatedLoggers.Single(l => l.Category == TestHelpers.LogCategory); var logMessages = logger.LogMessages.ToList(); Assert.Equal(153, logMessages.Count); } }
public async Task ActivityTriggerAsPOCO() { using (JobHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.ActivityTriggerAsPOCO), false)) { await host.StartAsync(); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToPOCO); var input = new { Foo = "Bar" }; startArgs.Input = input; var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); // The function echos back the 'Foo' input property value Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal(input.Foo, status?.Output); await host.StopAsync(); } }
public async Task HelloWorldOrchestration_Activity() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.SayHelloWithActivity) }; string activityFunctionName = nameof(TestActivities.Hello); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(HelloWorldOrchestration_Activity))) { await host.StartAsync(); var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], "World", this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal("World", status?.Input); Assert.Equal("Hello, World!", status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "HelloWorldOrchestration_Activity", orchestratorFunctionNames, activityFunctionName); } }
public async Task BindToBlobViaPOCO() { using (JobHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.BindToBlobViaPOCO), false)) { await host.StartAsync(); string connectionString = TestHelpers.GetStorageConnectionString(); CloudStorageAccount account = CloudStorageAccount.Parse(connectionString); this.output.WriteLine($"Using storage account: {account.Credentials.AccountName}"); // Blob and container names need to be kept in sync with the activity code. var data = new { InputPrefix = "Input", OutputPrefix = "Output", Suffix = 42, }; const string ContainerName = "test"; string inputBlobName = $"{data.InputPrefix}-{data.Suffix}"; string outputBlobName = $"{data.OutputPrefix}-{data.Suffix}"; CloudBlobClient blobClient = account.CreateCloudBlobClient(); CloudBlobContainer container = blobClient.GetContainerReference(ContainerName); if (await container.CreateIfNotExistsAsync()) { this.output.WriteLine($"Created container '{container.Name}'."); } string randomData = Guid.NewGuid().ToString("N"); this.output.WriteLine($"Creating blob named {outputBlobName}..."); CloudBlockBlob blob = container.GetBlockBlobReference(inputBlobName); await blob.UploadTextAsync(randomData); this.output.WriteLine($"Uploaded text '{randomData}' to {blob.Name}."); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToBlobViaJsonPayload); startArgs.Input = data; var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); this.output.WriteLine($"Searching for blob named {outputBlobName}..."); CloudBlockBlob newBlob = container.GetBlockBlobReference(outputBlobName); string copiedData = await newBlob.DownloadTextAsync(); this.output.WriteLine($"Downloaded text '{copiedData}' from {newBlob.Name}."); Assert.Equal(randomData, copiedData); await host.StopAsync(); } }
public async Task UnhandledActivityExceptionWithRetry() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.ActivityThrowWithRetry) }; string activityFunctionName = nameof(TestActivities.Throw); using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(UnhandledActivityExceptionWithRetry))) { await host.StartAsync(); string message = "Kah-BOOOOM!!!"; var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], message, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(40), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); // There aren't any exception details in the output: https://github.com/Azure/azure-functions-durable-extension/issues/84 Assert.StartsWith($"The activity function '{activityFunctionName}' failed.", (string)status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "UnhandledActivityExceptionWithRetry", orchestratorFunctionNames, activityFunctionName); } }
public async Task OrchestrationConcurrency() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(OrchestrationConcurrency))) { await host.StartAsync(); Func <Task> orchestrationStarter = async delegate() { var timeout = TimeSpan.FromSeconds(10); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Approval), timeout, this.output); await client.WaitForCompletionAsync(TimeSpan.FromSeconds(60), this.output); // Don't send any notification - let the internal timeout expire }; int iterations = 100; var tasks = new Task[iterations]; for (int i = 0; i < iterations; i++) { tasks[i] = orchestrationStarter(); } // The 100 orchestrations above (which each delay for 10 seconds) should all complete in less than 70 seconds. Task parallelOrchestrations = Task.WhenAll(tasks); Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(70)); Task winner = await Task.WhenAny(parallelOrchestrations, timeoutTask); Assert.Equal(parallelOrchestrations, winner); await host.StopAsync(); } }
public async Task BigReturnValue_Orchestrator() { string taskHub = nameof(BigReturnValue_Orchestrator); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, taskHub)) { await host.StartAsync(); var orchestrator = nameof(TestOrchestrations.BigReturnValue); var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); // The expected maximum payload size is 60 KB. // Strings in Azure Storage are encoded in UTF-32, which is 4 bytes per character. int stringLength = (61 * 1024) / 4; var client = await host.StartOrchestratorAsync(orchestrator, stringLength, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); Assert.True(status?.Output.ToString().Contains("The UTF-32 size of the JSON-serialized payload must not exceed 60 KB")); await host.StopAsync(); } }
public async Task TimerExpiration() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.Approval) }; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(TimerExpiration))) { await host.StartAsync(); var timeout = TimeSpan.FromSeconds(10); var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], timeout, this.output); // Need to wait for the instance to start before sending events to it. // TODO: This requirement may not be ideal and should be revisited. // BUG: https://github.com/Azure/azure-functions-durable-extension/issues/101 await client.WaitForStartupAsync(TimeSpan.FromSeconds(10), this.output); // Don't send any notification - let the internal timeout expire var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(20), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal("Expired", status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "TimerExpiration", orchestratorFunctionNames); } }
public async Task TerminateOrchestration() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.Counter) }; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(TerminateOrchestration))) { await host.StartAsync(); // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], 0, this.output); // Need to wait for the instance to start before we can terminate it. // TODO: This requirement may not be ideal and should be revisited. // BUG: https://github.com/Azure/azure-functions-durable-extension/issues/101 await client.WaitForStartupAsync(TimeSpan.FromSeconds(10), this.output); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10), this.output); Assert.Equal(OrchestrationRuntimeStatus.Terminated, status?.RuntimeStatus); Assert.Equal("sayōnara", status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "TerminateOrchestration", orchestratorFunctionNames); } }
public async Task SubOrchestration_ComplexType() { const string TaskHub = nameof(SubOrchestration_ComplexType); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, TaskHub)) { await host.StartAsync(); var complexTypeDataInput = new ComplexType { A = -42, B = new List <DateTime> { DateTime.UtcNow, DateTime.UtcNow.AddYears(1) }, C = ComplexType.CustomEnum.Value2, D = new ComplexType.ComplexInnerType { E = Guid.NewGuid().ToString(), F = TimeSpan.FromHours(1.5), }, }; var input = new StartOrchestrationArgs { FunctionName = nameof(TestOrchestrations.CallActivity), Input = new StartOrchestrationArgs { FunctionName = nameof(TestActivities.Echo), Input = complexTypeDataInput, }, }; string parentOrchestrator = nameof(TestOrchestrations.CallOrchestrator); var client = await host.StartOrchestratorAsync(parentOrchestrator, input, this.output); var status = await client.WaitForCompletionAsync( Debugger.IsAttached?TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(20), this.output); Assert.NotNull(status); Assert.Equal(OrchestrationRuntimeStatus.Completed, status.RuntimeStatus); Assert.Equal(client.InstanceId, status.InstanceId); Assert.NotNull(status.Output); ComplexType complextTypeDataOutput = status.Output.ToObject <ComplexType>(); Assert.NotNull(complextTypeDataOutput); Assert.Equal(complexTypeDataInput.A, complextTypeDataOutput.A); Assert.Equal(complexTypeDataInput.B[0], complextTypeDataOutput.B[0]); Assert.Equal(complexTypeDataInput.B[1], complextTypeDataOutput.B[1]); Assert.NotNull(complextTypeDataOutput.D); Assert.Equal(complexTypeDataInput.D.E, complextTypeDataOutput.D.E); Assert.Equal(complexTypeDataInput.D.F, complextTypeDataOutput.D.F); await host.StopAsync(); } }
public async Task BindToBlobViaParameterName() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(this.ActivityTriggerAsNumber))) { await host.StartAsync(); string connectionString = AmbientConnectionStringProvider.Instance.GetConnectionString(ConnectionStringNames.Storage); CloudStorageAccount account = CloudStorageAccount.Parse(connectionString); this.output.WriteLine($"Using storage account: {account.Credentials.AccountName}"); // Blob and container names need to be kept in sync with the activity code. const string OriginalBlobName = "MyBlob"; const string UpdatedBlobName = OriginalBlobName + "-archive"; const string ContainerName = "test"; CloudBlobClient blobClient = account.CreateCloudBlobClient(); CloudBlobContainer container = blobClient.GetContainerReference(ContainerName); if (await container.CreateIfNotExistsAsync()) { this.output.WriteLine($"Created container '{container.Name}'."); } string randomData = Guid.NewGuid().ToString("N"); CloudBlockBlob blob = container.GetBlockBlobReference(OriginalBlobName); await blob.UploadTextAsync(randomData); this.output.WriteLine($"Uploaded text '{randomData}' to {blob.Name}."); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToBlobViaParameterName); startArgs.Input = OriginalBlobName; var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); CloudBlockBlob newBlob = container.GetBlockBlobReference(UpdatedBlobName); string copiedData = await newBlob.DownloadTextAsync(); this.output.WriteLine($"Downloaded text '{copiedData}' from {newBlob.Name}."); Assert.Equal(randomData, copiedData); await host.StopAsync(); } }
public async Task Orchestration_OnValidOrchestrator() { const string greetingName = "ValidOrchestrator"; const string validOrchestratorName = "SayHelloWithActivity"; string[] orchestratorFunctionNames = { nameof(TestOrchestrations.CallOrchestrator), validOrchestratorName }; string activityFunctionName = nameof(TestActivities.Hello); var input = new { Foo = greetingName }; var inputJson = JsonConvert.SerializeObject(input); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(Orchestration_OnValidOrchestrator))) { await host.StartAsync(); var startArgs = new StartOrchestrationArgs { FunctionName = orchestratorFunctionNames[1], Input = inputJson }; // Function type call chain: 'CallActivity' (orchestrator) -> 'SayHelloWithActivity' (orchestrator) -> 'Hello' (activity) var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], startArgs, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); var statusInput = JsonConvert.DeserializeObject <Dictionary <string, object> >(status?.Input.ToString()); Assert.NotNull(status); Assert.Equal(OrchestrationRuntimeStatus.Completed, status.RuntimeStatus); Assert.Equal(client.InstanceId, status.InstanceId); Assert.Equal(validOrchestratorName, statusInput["FunctionName"].ToString()); Assert.Contains(greetingName, statusInput["Input"].ToString()); Assert.Equal($"Hello, {inputJson}!", status.Output.ToString()); await host.StopAsync(); if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "Orchestration_OnValidOrchestrator", orchestratorFunctionNames, activityFunctionName); } } }
public async Task HandledActivityException() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(HandledActivityException))) { await host.StartAsync(); // Empty string input should result in ArgumentNullException in the orchestration code. var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.TryCatchLoop), 5, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal(5, status?.Output); await host.StopAsync(); } }
public async Task ParallelOrchestration() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(ParallelOrchestration))) { await host.StartAsync(); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.DiskUsage), Environment.CurrentDirectory, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(90), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal(Environment.CurrentDirectory, status?.Input); Assert.True((long?)status?.Output > 0L); await host.StopAsync(); } }
public async Task ActivityWithRetry_NullRetryOptions() { using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(ActivityWithRetry_NullRetryOptions))) { await host.StartAsync(); string message = "Kah-BOOOOM!!!"; string orchestratorFunctionName = nameof(TestOrchestrations.ActivityWithRetry_NullRetryOptions); var client = await host.StartOrchestratorAsync(orchestratorFunctionName, message, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(40), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); Assert.True(status?.Output.ToString().Contains("Value cannot be null.")); Assert.True(status?.Output.ToString().Contains("Parameter name: retryOptions")); await host.StopAsync(); } }
public async Task ThrowExceptionOnLongTimer() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(Orchestration_OnValidOrchestrator))) { await host.StartAsync(); // Right now, the limit for timers is 6 days. In the future, we'll extend this and update this test. // https://github.com/Azure/azure-functions-durable-extension/issues/14 DateTime fireAt = DateTime.UtcNow.AddDays(7); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Timer), fireAt, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.NotNull(status); Assert.Equal(OrchestrationRuntimeStatus.Failed, status.RuntimeStatus); Assert.True(status.Output.ToString().Contains("fireAt")); await host.StopAsync(); } }
public async Task ActivityTriggerAsNumber() { using (JobHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.ActivityTriggerAsNumber), false)) { await host.StartAsync(); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToDouble); startArgs.Input = 3.14; var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(this.output); // The function echos back the input value Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal((double)startArgs.Input, status?.Output); await host.StopAsync(); } }
public async Task Orchestration_OnUnregisteredOrchestrator() { const string unregisteredOrchestrator = "UnregisteredOrchestrator"; string[] orchestratorFunctionNames = { nameof(TestOrchestrations.CallOrchestrator), unregisteredOrchestrator }; string errorMessage = $"The function '{unregisteredOrchestrator}' doesn't exist, is disabled, or is not an orchestrator function"; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(Orchestration_OnUnregisteredActivity))) { await host.StartAsync(); var startArgs = new StartOrchestrationArgs { FunctionName = unregisteredOrchestrator, Input = new { Foo = "Bar" } }; var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], startArgs, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); Assert.True(status?.Output.ToString().Contains(errorMessage)); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "Orchestration_OnUnregisteredOrchestrator", orchestratorFunctionNames); } }
public async Task ActivityTriggerAsNumber() { using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(ActivityTriggerAsNumber))) { await host.StartAsync(); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToDouble); startArgs.Input = 3.14; var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); // The function echos back the input value Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal((double)startArgs.Input, status?.Output); await host.StopAsync(); } }
public async Task OrchestrationWithRetry_NullRetryOptions() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.OrchestratorWithRetry_NullRetryOptions) }; using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(OrchestrationWithRetry_NullRetryOptions))) { await host.StartAsync(); // Null input should result in ArgumentNullException in the orchestration code. var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], null, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(50), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); Assert.True(status?.Output.ToString().Contains("Value cannot be null.")); Assert.True(status?.Output.ToString().Contains("Parameter name: retryOptions")); await host.StopAsync(); } }
public async Task ActivityTriggerAsJObject() { using (JobHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.ActivityTriggerAsJObject), false)) { await host.StartAsync(); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToJObject); startArgs.Input = new { Foo = "Bar" }; var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(this.output); // The function checks to see if there is a property called "Foo" which is set to a value // called "Bar" and returns true if this is the case. Otherwise returns false. Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.True((bool)status?.Output); await host.StopAsync(); } }
public async Task Orchestration_OnUnregisteredActivity() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.CallActivity) }; const string activityFunctionName = "UnregisteredActivity"; string errorMessage = $"The function '{activityFunctionName}' doesn't exist, is disabled, or is not an activity function"; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(Orchestration_OnUnregisteredActivity))) { await host.StartAsync(); var startArgs = new StartOrchestrationArgs { FunctionName = activityFunctionName, Input = new { Foo = "Bar" } }; var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], startArgs, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); // There aren't any exception details in the output: https://github.com/Azure/azure-functions-durable-extension/issues/84 Assert.StartsWith(errorMessage, (string)status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "Orchestration_OnUnregisteredActivity", orchestratorFunctionNames); } }
public async Task RaiseEventToSubOrchestration() { string taskHub = nameof(RaiseEventToSubOrchestration); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, taskHub)) { await host.StartAsync(); var orchestrator = nameof(TestOrchestrations.CallOrchestrator); var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var input = new StartOrchestrationArgs { FunctionName = nameof(TestOrchestrations.Approval), InstanceId = "SubOrchestration-" + Guid.NewGuid().ToString("N"), Input = TimeSpan.FromMinutes(5) }; var client = await host.StartOrchestratorAsync(orchestrator, input, this.output); var status = await client.WaitForStartupAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Running, status?.RuntimeStatus); // Wait long enough for the sub-orchestration to be started and waiting for input. await Task.Delay(TimeSpan.FromSeconds(2)); await client.InnerClient.RaiseEventAsync(input.InstanceId, "approval", true); status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal("Approved", status?.Output); await host.StopAsync(); } }
public async Task StartOrchestration_OnUnregisteredOrchestrator() { const string activityFunctionName = "UnregisteredOrchestrator"; string errorMessage = $"The function '{activityFunctionName}' doesn't exist, is disabled, or is not an orchestrator function"; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(StartOrchestration_OnUnregisteredOrchestrator))) { await host.StartAsync(); Exception ex = await Assert.ThrowsAsync <FunctionInvocationException>(async() => await host.StartOrchestratorAsync("UnregisteredOrchestrator", "Unregistered", this.output)); Assert.NotNull(ex.InnerException); Assert.Contains(errorMessage, ex.InnerException?.ToString()); await host.StopAsync(); } }
public async Task OrchestrationStartAndCompleted() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.SayHelloInline), }; var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; var callCount = 0; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(this.OrchestrationStartAndCompleted), eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); var extensionRegistry = (IExtensionRegistry)host.Services.GetService(typeof(IExtensionRegistry)); var extensionProviders = extensionRegistry.GetExtensions(typeof(IExtensionConfigProvider)) .Where(x => x is DurableTaskExtension) .ToList(); if (extensionProviders.Any()) { var extension = (DurableTaskExtension)extensionProviders.First(); var mock = new Mock <HttpMessageHandler>(); mock.Protected() .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>()) .Returns((HttpRequestMessage request, CancellationToken cancellationToken) => { Assert.True(request.Headers.Any(x => x.Key == "aeg-sas-key")); var values = request.Headers.GetValues("aeg-sas-key").ToList(); Assert.Single(values); Assert.Equal(eventGridKeyValue, values[0]); Assert.Equal(eventGridEndpoint, request.RequestUri.ToString()); var json = request.Content.ReadAsStringAsync().GetAwaiter().GetResult(); dynamic content = JsonConvert.DeserializeObject(json); foreach (dynamic o in content) { Assert.Equal("1.0", o.dataVersion.ToString()); Assert.Equal(nameof(this.OrchestrationStartAndCompleted), o.data.HubName.ToString()); Assert.Equal(orchestratorFunctionNames[0], o.data.FunctionName.ToString()); if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("0", o.data.EventType.ToString()); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Completed", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("1", o.data.EventType.ToString()); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } } callCount++; var message = new HttpResponseMessage(HttpStatusCode.OK); message.Content = new StringContent("{\"message\":\"OK!\"}"); return(Task.FromResult(message)); }); extension.LifeCycleNotificationHelper.SetHttpMessageHandler(mock.Object); } var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], "World", this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal("World", status?.Input); Assert.Equal("Hello, World!", status?.Output); Assert.Equal(2, callCount); await host.StopAsync(); } }
public async Task OrchestrationTerminate() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.Counter), }; var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; var callCount = 0; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(this.OrchestrationTerminate), eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); var extensionRegistry = (IExtensionRegistry)host.Services.GetService(typeof(IExtensionRegistry)); var extensionProviders = extensionRegistry.GetExtensions(typeof(IExtensionConfigProvider)) .Where(x => x is DurableTaskExtension) .ToList(); if (extensionProviders.Any()) { var extension = (DurableTaskExtension)extensionProviders.First(); var mock = new Mock <HttpMessageHandler>(); mock.Protected() .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>()) .Returns((HttpRequestMessage request, CancellationToken cancellationToken) => { Assert.True(request.Headers.Any(x => x.Key == "aeg-sas-key")); var values = request.Headers.GetValues("aeg-sas-key").ToList(); Assert.Single(values); Assert.Equal(eventGridKeyValue, values[0]); Assert.Equal(eventGridEndpoint, request.RequestUri.ToString()); var json = request.Content.ReadAsStringAsync().GetAwaiter().GetResult(); dynamic content = JsonConvert.DeserializeObject(json); foreach (dynamic o in content) { Assert.Equal("1.0", o.dataVersion.ToString()); Assert.Equal(nameof(this.OrchestrationTerminate), o.data.HubName.ToString()); Assert.Equal(orchestratorFunctionNames[0], o.data.FunctionName.ToString()); if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("0", o.data.EventType.ToString()); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Terminated", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("5", o.data.EventType.ToString()); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } } callCount++; var message = new HttpResponseMessage(HttpStatusCode.OK); message.Content = new StringContent("{\"message\":\"OK!\"}"); return(Task.FromResult(message)); }); extension.LifeCycleNotificationHelper.SetHttpMessageHandler(mock.Object); } // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], 0, this.output); // Need to wait for the instance to start before we can terminate it. // TODO: This requirement may not be ideal and should be revisited. // BUG: https://github.com/Azure/azure-functions-durable-extension/issues/101 await client.WaitForStartupAsync(TimeSpan.FromSeconds(30), this.output); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Terminated, status?.RuntimeStatus); Assert.Equal("sayōnara", status?.Output); await host.StopAsync(); } }
public async Task OrchestrationStartAndCompleted(bool extendedSessionsEnabled) { var functionName = nameof(TestOrchestrations.SayHelloInline); var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; using (JobHost host = TestHelpers.GetJobHost( this.loggerFactory, nameof(this.OrchestrationStartAndCompleted), extendedSessionsEnabled, eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); string createdInstanceId = Guid.NewGuid().ToString("N"); Func <HttpRequestMessage, HttpResponseMessage> responseGenerator = (HttpRequestMessage req) => req.CreateResponse(HttpStatusCode.OK, "{\"message\":\"OK!\"}"); int callCount = 0; List <Action> eventGridRequestValidators = this.ConfigureEventGridMockHandler( host, functionName, createdInstanceId, eventGridKeyValue, eventGridEndpoint, responseGenerator, handler: (JObject eventPayload) => { dynamic o = eventPayload; if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Running", (string)o.data.runtimeStatus); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Completed", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Completed", (string)o.data.runtimeStatus); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } callCount++; }); var client = await host.StartOrchestratorAsync( functionName, "World", this.output, createdInstanceId); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal("World", status?.Input); Assert.Equal("Hello, World!", status?.Output); // There should be one validator for each Event Grid request. // Each validator is a delegate with several Assert statements. Assert.NotEmpty(eventGridRequestValidators); foreach (Action validator in eventGridRequestValidators) { validator.Invoke(); } Assert.Equal(2, callCount); await host.StopAsync(); } }
public async Task OrchestrationTerminate(bool extendedSessionsEnabled) { // Using the counter orchestration because it will wait indefinitely for input. var functionName = nameof(TestOrchestrations.Counter); var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; using (JobHost host = TestHelpers.GetJobHost( this.loggerFactory, nameof(this.OrchestrationTerminate), extendedSessionsEnabled, eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); string createdInstanceId = Guid.NewGuid().ToString("N"); Func <HttpRequestMessage, HttpResponseMessage> responseGenerator = (HttpRequestMessage req) => req.CreateResponse(HttpStatusCode.OK, "{\"message\":\"OK!\"}"); int callCount = 0; List <Action> eventGridRequestValidators = this.ConfigureEventGridMockHandler( host, functionName, createdInstanceId, eventGridKeyValue, eventGridEndpoint, responseGenerator, handler: (JObject eventPayload) => { dynamic o = eventPayload; if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Running", (string)o.data.runtimeStatus); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Terminated", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Terminated", (string)o.data.runtimeStatus); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } callCount++; }); var client = await host.StartOrchestratorAsync( functionName, 0, this.output, createdInstanceId); await client.WaitForStartupAsync(TimeSpan.FromSeconds(30), this.output); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Terminated, status?.RuntimeStatus); Assert.Equal("sayōnara", status?.Output); // There should be one validator for each Event Grid request. // Each validator is a delegate with several Assert statements. Assert.NotEmpty(eventGridRequestValidators); foreach (Action validator in eventGridRequestValidators) { validator.Invoke(); } // TODO: There should be two calls, but the termination notification is not being fired. // https://github.com/Azure/azure-functions-durable-extension/issues/286 Assert.Equal(1, callCount); await host.StopAsync(); } }