public async Task TestLocalRcpEndpointRuntimeVersion(string runtimeVersion, bool enabledExpected) { INameResolver nameResolver = new SimpleNameResolver( new Dictionary <string, string> { { "FUNCTIONS_WORKER_RUNTIME", runtimeVersion }, }); using (ITestHost host = TestHelpers.GetJobHost( this.loggerProvider, nameof(this.TestLocalRcpEndpointRuntimeVersion), enableExtendedSessions: false, localRpcEndpointEnabled: null /* use FUNCTIONS_WORKER_RUNTIME to decide */, nameResolver: nameResolver)) { await host.StartAsync(); // Check to see whether the local RPC endpoint has been opened IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); IPEndPoint[] endpoints = ipGlobalProperties.GetActiveTcpListeners(); const string LocalRcpAddress = "127.0.0.1:17071"; if (enabledExpected) { Assert.Contains(LocalRcpAddress, endpoints.Select(ep => ep.ToString())); } else { Assert.DoesNotContain(LocalRcpAddress, endpoints.Select(ep => ep.ToString())); } await host.StopAsync(); } }
public async Task TestLocalRcpEndpointRuntimeVersion(string runtimeVersion, bool enabledExpected) { INameResolver nameResolver = new SimpleNameResolver( new Dictionary <string, string> { { "FUNCTIONS_WORKER_RUNTIME", runtimeVersion }, }); using (ITestHost host = TestHelpers.GetJobHost( this.loggerProvider, nameof(this.TestLocalRcpEndpointRuntimeVersion), enableExtendedSessions: false, localRpcEndpointEnabled: null /* use FUNCTIONS_WORKER_RUNTIME to decide */, nameResolver: nameResolver)) { await host.StartAsync(); // Validate if we opened local RPC endpoint by looking at log statements. var logger = this.loggerProvider.CreatedLoggers.Single(l => l.Category == TestHelpers.LogCategory); var logMessages = logger.LogMessages.ToList(); bool enabledRpcEndpoint = logMessages.Any(msg => msg.Level == Microsoft.Extensions.Logging.LogLevel.Information && msg.FormattedMessage.StartsWith("Opened local RPC endpoint:")); Assert.Equal(enabledExpected, enabledRpcEndpoint); await host.StopAsync(); } }
public async Task BindToBlobViaPOCO(string storageProviderType) { using (ITestHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.BindToBlobViaPOCO), false, storageProviderType)) { 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 client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(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 TimerLengthLessThanMaxTime(bool extendedSessions) { using (ITestHost host = TestHelpers.GetJobHost( this.loggerProvider, nameof(this.TimerLengthLessThanMaxTime), extendedSessions, storageProviderType: "azure_storage", durabilityProviderFactoryType: typeof(AzureStorageShortenedTimerDurabilityProviderFactory))) { await host.StartAsync(); var fireAt = DateTime.UtcNow.AddSeconds(30); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Timer), fireAt, this.output); var status = await client.WaitForCompletionAsync(this.output, timeout : TimeSpan.FromMinutes(2)); Assert.Equal(OrchestrationRuntimeStatus.Completed, status.RuntimeStatus); await host.StopAsync(); } }
public async Task ActivityTriggerAsNumber(string storageProviderType) { using (ITestHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.ActivityTriggerAsNumber), false, storageProviderType)) { 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 WaitForExternalEventAboveMaximumTimerLength() { using (ITestHost host = TestHelpers.GetJobHost( this.loggerProvider, nameof(this.WaitForExternalEventAboveMaximumTimerLength), enableExtendedSessions: false, storageProviderType: "azure_storage", durabilityProviderFactoryType: typeof(AzureStorageShortenedTimerDurabilityProviderFactory))) { await host.StartAsync(); var fireAt = TimeSpan.FromSeconds(90); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.ApprovalWithTimeout), (fireAt, "throw"), this.output); var status = await client.WaitForCompletionAsync(this.output, timeout : TimeSpan.FromMinutes(2)); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal("TimeoutException", status?.Output); await host.StopAsync(); } }
public async Task InvokeLocalRpcEndpoint() { using (ITestHost host = TestHelpers.GetJobHost( this.loggerProvider, nameof(this.InvokeLocalRpcEndpoint), enableExtendedSessions: false, localRpcEndpointEnabled: true, notificationUrl: null)) { await host.StartAsync(); using (var client = new WebClient()) { string jsonString = client.DownloadString("http://localhost:17071/durabletask/instances"); // The result is expected to be an empty array JArray array = JArray.Parse(jsonString); } await host.StopAsync(); } }
private async Task ActivityTriggerAsJObject(string storageProviderType) { using (ITestHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.ActivityTriggerAsJObject), false, storageProviderType)) { 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 BindToDurableClientAsString(bool localRcpEnabled) { Uri testNotificationUrl = new Uri("https://durable.edu/runtime/webhooks/durabletask?code=abcdefg"); using (ITestHost host = TestHelpers.GetJobHost( this.loggerProvider, nameof(this.BindToDurableClientAsString) + localRcpEnabled.ToString(), enableExtendedSessions: false, localRpcEndpointEnabled: localRcpEnabled, notificationUrl: testNotificationUrl)) { await host.StartAsync(); // Fetch the JSON that was passed to the DurableClient binding string[] jsonRef = new string[1]; await host.CallAsync( nameof(ClientFunctions.GetDurableClientConfigJson), new Dictionary <string, object> { { "jsonRef", jsonRef }, }); string jsonString = jsonRef[0]; Assert.NotNull(jsonString); JObject outerJson = JObject.Parse(jsonString); // Expected format: // { // "taskHubName": "BindToDurableClientAsStringV2", // "creationUrls": { // "createNewInstancePostUri": "https://durable.edu/runtime/webhooks/durabletask/orchestrators/{functionName}[/{instanceId}]", // "createAndWaitOnNewInstancePostUri": "https://durable.edu/runtime/webhooks/durabletask/orchestrators/{functionName}[/{instanceId}]?timeout={timeoutInSeconds}&pollingInterval={intervalInSeconds}" // }, // "managementUrls": { // "id": "INSTANCEID", // "statusQueryGetUri": "https://durable.edu/runtime/webhooks/durabletask/instances/INSTANCEID?taskHub=BindToDurableClientAsStringV2&connection=Storage", // "sendEventPostUri": "https://durable.edu/runtime/webhooks/durabletask/instances/INSTANCEID/raiseEvent/{eventName}?taskHub=BindToDurableClientAsStringV2&connection=Storage", // "terminatePostUri": "https://durable.edu/runtime/webhooks/durabletask/instances/INSTANCEID/terminate?reason={text}&taskHub=BindToDurableClientAsStringV2&connection=Storage", // "rewindPostUri": "https://durable.edu/runtime/webhooks/durabletask/instances/INSTANCEID/rewind?reason={text}&taskHub=BindToDurableClientAsStringV2&connection=Storage", // "purgeHistoryDeleteUri": "https://durable.edu/runtime/webhooks/durabletask/instances/INSTANCEID?taskHub=BindToDurableClientAsStringV2&connection=Storage" // }, // "baseUrl": "https://durable.edu/runtime/webhooks/durabletask/", // "requiredQueryStringParameters": "code=abcdefg", // "rpcBaseUrl": "http://127.0.0.1:17071/durabletask/" (or null) // } Assert.True(outerJson.TryGetValue("taskHubName", out JToken taskHubName)); Assert.StartsWith(nameof(this.BindToDurableClientAsString), (string)taskHubName); // Local function that validates presence and format of the URL payloads. void CommonUriValidation(JToken json, string fieldName, string[] requiredSegments) { Assert.Equal(JTokenType.Object, json.Type); JObject jObj = (JObject)json; Assert.True(jObj.TryGetValue(fieldName, out JToken fieldValue)); Assert.True(Uri.TryCreate((string)fieldValue, UriKind.Absolute, out Uri uri)); Assert.StartsWith(testNotificationUrl.GetLeftPart(UriPartial.Path), uri.GetLeftPart(UriPartial.Path)); if (fieldName != "baseUrl") { Assert.Contains(testNotificationUrl.Query.TrimStart('?'), uri.Query); } foreach (string segment in requiredSegments) { Assert.Contains(segment, uri.OriginalString); } } string[] creationUrlParams = new[] { "{functionName}", "{instanceId}" }; // Legacy payload validation Assert.True(outerJson.TryGetValue("creationUrls", out JToken creationUrls)); CommonUriValidation(creationUrls, "createNewInstancePostUri", creationUrlParams); CommonUriValidation(creationUrls, "createAndWaitOnNewInstancePostUri", creationUrlParams); Assert.True(outerJson.TryGetValue("managementUrls", out JToken managementUrls)); Assert.Equal(JTokenType.Object, managementUrls.Type); Assert.True(((JObject)managementUrls).TryGetValue("id", out JToken idValue)); Assert.Equal(JTokenType.String, idValue.Type); string idPlaceholder = (string)idValue; string[] managementUrlParams = new[] { idPlaceholder }; CommonUriValidation(managementUrls, "statusQueryGetUri", managementUrlParams); CommonUriValidation(managementUrls, "sendEventPostUri", managementUrlParams); CommonUriValidation(managementUrls, "terminatePostUri", managementUrlParams); CommonUriValidation(managementUrls, "rewindPostUri", managementUrlParams); CommonUriValidation(managementUrls, "purgeHistoryDeleteUri", managementUrlParams); CommonUriValidation(outerJson, "baseUrl", new string[0]); Assert.True(outerJson.TryGetValue("requiredQueryStringParameters", out JToken requiredQueryStringParameters)); Assert.Equal(testNotificationUrl.Query.Trim('?'), (string)requiredQueryStringParameters); Assert.True(outerJson.TryGetValue("rpcBaseUrl", out JToken rpcBaseUrl)); if (localRcpEnabled) { Assert.True(Uri.TryCreate((string)rpcBaseUrl, UriKind.Absolute, out Uri rpcBaseUri)); Assert.True(rpcBaseUri.IsLoopback); Assert.Equal("http", rpcBaseUri.Scheme); Assert.Equal("/durabletask/", rpcBaseUri.AbsolutePath); } else { Assert.Equal(JTokenType.Null, rpcBaseUrl.Type); } } }