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);
            }
        }
Exemple #4
0
        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);
            }
        }
Exemple #6
0
        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();
            }
        }
Exemple #19
0
        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);
            }
        }
Exemple #21
0
        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();
            }
        }
Exemple #23
0
        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();
            }
        }