diff --git a/src/Invictus.Testing.Tests.Integration/IntegrationTest.cs b/src/Invictus.Testing.Tests.Integration/IntegrationTest.cs
new file mode 100644
index 0000000..19d24a1
--- /dev/null
+++ b/src/Invictus.Testing.Tests.Integration/IntegrationTest.cs
@@ -0,0 +1,59 @@
+using Arcus.Testing.Logging;
+using Microsoft.Extensions.Logging;
+using Xunit.Abstractions;
+
+namespace Invictus.Testing.Tests.Integration
+{
+ ///
+ /// Provides set of reusable information required for the logic app integration tests.
+ ///
+ public abstract class IntegrationTest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected IntegrationTest(ITestOutputHelper outputWriter)
+ {
+ Logger = new XunitTestLogger(outputWriter);
+ ResourceGroup = Configuration.GetAzureResourceGroup();
+ LogicAppName = Configuration.GetTestLogicAppName();
+ LogicAppMockingName = Configuration.GetTestMockingLogicAppName();
+
+ string subscriptionId = Configuration.GetAzureSubscriptionId();
+ string tenantId = Configuration.GetAzureTenantId();
+ string clientId = Configuration.GetAzureClientId();
+ string clientSecret = Configuration.GetAzureClientSecret();
+ Authentication = LogicAuthentication.UsingServicePrincipal(tenantId, subscriptionId, clientId, clientSecret);
+ }
+
+ ///
+ /// Gets the logger to write diagnostic messages during tests.
+ ///
+ protected ILogger Logger { get; }
+
+ ///
+ /// Gets the configuration available in the current integration test suite.
+ ///
+ protected TestConfig Configuration { get; } = TestConfig.Create();
+
+ ///
+ /// Gets the resource group where the logic app resources on Azure are located.
+ ///
+ protected string ResourceGroup { get; }
+
+ ///
+ /// Gets the name of the logic app resource running on Azure to test stateless operations against.
+ ///
+ protected string LogicAppName { get; }
+
+ ///
+ /// Gets the name of the logic app resource running on Azure to test stateful operations against.
+ ///
+ protected string LogicAppMockingName { get; }
+
+ ///
+ /// Gets the authentication mechanism to authenticate with Azure.
+ ///
+ protected LogicAuthentication Authentication { get; }
+ }
+}
diff --git a/src/Invictus.Testing.Tests.Integration/Invictus.Testing.Tests.Integration.csproj b/src/Invictus.Testing.Tests.Integration/Invictus.Testing.Tests.Integration.csproj
index f993de8..49faa08 100644
--- a/src/Invictus.Testing.Tests.Integration/Invictus.Testing.Tests.Integration.csproj
+++ b/src/Invictus.Testing.Tests.Integration/Invictus.Testing.Tests.Integration.csproj
@@ -5,6 +5,7 @@
+
diff --git a/src/Invictus.Testing.Tests.Integration/LogicAppClientTests.cs b/src/Invictus.Testing.Tests.Integration/LogicAppClientTests.cs
new file mode 100644
index 0000000..8bfaecc
--- /dev/null
+++ b/src/Invictus.Testing.Tests.Integration/LogicAppClientTests.cs
@@ -0,0 +1,263 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Invictus.Testing.Model;
+using Invictus.Testing.Serialization;
+using Newtonsoft.Json.Linq;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Invictus.Testing.Tests.Integration
+{
+ public class LogicAppClientTests : IntegrationTest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LogicAppClientTests(ITestOutputHelper outputWriter) : base(outputWriter)
+ {
+ }
+
+ [Fact]
+ public async Task GetLogicAppTriggerUrl_NoTriggerNameSpecified_Success()
+ {
+ // Arrange
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication))
+ {
+ // Act
+ LogicAppTriggerUrl logicAppTriggerUrl = await logicApp.GetTriggerUrlAsync();
+
+ // Assert
+ Assert.NotNull(logicAppTriggerUrl.Value);
+ Assert.Equal("POST", logicAppTriggerUrl.Method);
+ }
+ }
+
+ [Fact]
+ public async Task GetLogicAppTriggerUrl_ByName_Success()
+ {
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication))
+ {
+ // Act
+ LogicAppTriggerUrl logicAppTriggerUrl = await logicApp.GetTriggerUrlByNameAsync(triggerName: "manual");
+
+ // Assert
+ Assert.NotNull(logicAppTriggerUrl.Value);
+ Assert.Equal("POST", logicAppTriggerUrl.Method);
+ }
+ }
+
+ [Fact]
+ public async Task TemporaryEnableSuccessStaticResultForAction_WithoutConsumerStaticResult_Success()
+ {
+ // Arrange
+ const string actionName = "HTTP";
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId },
+ };
+
+ // Act
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppMockingName, Authentication, Logger))
+ {
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ await using (await logicApp.TemporaryEnableSuccessStaticResultAsync(actionName))
+ {
+ // Act
+ await logicApp.TriggerAsync(headers);
+ LogicAppAction enabledAction = await PollForLogicAppActionAsync(correlationId, actionName);
+
+ Assert.Equal(actionName, enabledAction.Name);
+ Assert.Equal("200", enabledAction.Outputs.statusCode.ToString());
+ Assert.Equal("Succeeded", enabledAction.Status);
+ }
+
+ await logicApp.TriggerAsync(headers);
+ LogicAppAction disabledAction = await PollForLogicAppActionAsync(correlationId, actionName);
+
+ Assert.NotEmpty(disabledAction.Outputs.headers);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task TemporaryEnableStaticResultsForAction_WithSuccessStaticResult_Success()
+ {
+ // Arrange
+ const string actionName = "HTTP";
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId },
+ };
+
+ var definition = new StaticResultDefinition
+ {
+ Outputs = new Outputs
+ {
+ Headers = new Dictionary { { "testheader", "testvalue" } },
+ StatusCode = "200",
+ Body = JToken.Parse("{id : 12345, name : 'test body'}")
+ },
+ Status = "Succeeded"
+ };
+
+ // Act
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppMockingName, Authentication, Logger))
+ {
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ var definitions = new Dictionary { [actionName] = definition };
+ await using (await logicApp.TemporaryEnableStaticResultsAsync(definitions))
+ {
+ // Act
+ await logicApp.TriggerAsync(headers);
+ LogicAppAction enabledAction = await PollForLogicAppActionAsync(correlationId, actionName);
+
+ Assert.Equal("200", enabledAction.Outputs.statusCode.ToString());
+ Assert.Equal("testvalue", enabledAction.Outputs.headers["testheader"].ToString());
+ Assert.Contains("test body", enabledAction.Outputs.body.ToString());
+ }
+
+ await logicApp.TriggerAsync(headers);
+ LogicAppAction disabledAction = await PollForLogicAppActionAsync(correlationId, actionName);
+
+ Assert.DoesNotContain("test body", disabledAction.Outputs.body.ToString());
+ }
+ }
+ }
+
+ [Fact]
+ public async Task TemporaryEnableStaticResultForAction_WithSuccessStaticResult_Success()
+ {
+ // Arrange
+ const string actionName = "HTTP";
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId },
+ };
+
+ var definition = new StaticResultDefinition
+ {
+ Outputs = new Outputs
+ {
+ Headers = new Dictionary { { "testheader", "testvalue" } },
+ StatusCode = "200",
+ Body = JToken.Parse("{id : 12345, name : 'test body'}")
+ },
+ Status = "Succeeded"
+ };
+
+ // Act
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppMockingName, Authentication))
+ {
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ await using (await logicApp.TemporaryEnableStaticResultAsync(actionName, definition))
+ {
+ // Act
+ await logicApp.TriggerAsync(headers);
+ LogicAppAction enabledAction = await PollForLogicAppActionAsync(correlationId, actionName);
+
+ Assert.Equal("200", enabledAction.Outputs.statusCode.ToString());
+ Assert.Equal("testvalue", enabledAction.Outputs.headers["testheader"].ToString());
+ Assert.Contains("test body", enabledAction.Outputs.body.ToString());
+ }
+
+ await logicApp.TriggerAsync(headers);
+ LogicAppAction disabledAction = await PollForLogicAppActionAsync(correlationId, actionName);
+
+ Assert.DoesNotContain("test body", disabledAction.Outputs.body.ToString());
+ }
+ }
+ }
+
+ [Fact]
+ public async Task TemporaryEnableLogicApp_Success()
+ {
+ // Act
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppMockingName, Authentication, Logger))
+ {
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ // Assert
+ LogicApp metadata = await logicApp.GetMetadataAsync();
+ Assert.Equal("Enabled", metadata.State);
+ }
+ {
+ LogicApp metadata = await logicApp.GetMetadataAsync();
+ Assert.Equal("Disabled", metadata.State);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public async Task Constructor_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ await Assert.ThrowsAsync(
+ () => LogicAppClient.CreateAsync(resourceGroup, LogicAppName, Authentication));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public async Task Constructor_WithBlankLogicApp_Fails(string logicApp)
+ {
+ await Assert.ThrowsAsync(
+ () => LogicAppClient.CreateAsync(ResourceGroup, logicApp, Authentication));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public async Task ConstructorWithLogger_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ await Assert.ThrowsAsync(
+ () => LogicAppClient.CreateAsync(resourceGroup, LogicAppName, Authentication, Logger));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public async Task ConstructorWithLogger_WithBlankLogicApp_Fails(string logicApp)
+ {
+ await Assert.ThrowsAsync(
+ () => LogicAppClient.CreateAsync(ResourceGroup, logicApp, Authentication, Logger));
+ }
+
+ [Fact]
+ public async Task Constructor_WithNullAuthentication_Fails()
+ {
+ await Assert.ThrowsAnyAsync(
+ () => LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, authentication: null));
+ }
+
+ [Fact]
+ public async Task ConstructorWithLogger_WithNullAuthentication_Fails()
+ {
+ await Assert.ThrowsAnyAsync(
+ () => LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, authentication: null, logger: Logger));
+ }
+
+ private async Task PollForLogicAppActionAsync(string correlationId, string actionName)
+ {
+ LogicAppRun logicAppRun = await LogicAppsProvider
+ .LocatedAt(ResourceGroup, LogicAppMockingName, Authentication, Logger)
+ .WithStartTime(DateTimeOffset.UtcNow.AddMinutes(-1))
+ .WithCorrelationId(correlationId)
+ .PollForSingleLogicAppRunAsync();
+
+ Assert.True(logicAppRun.Actions.Count() != 0);
+ LogicAppAction logicAppAction = logicAppRun.Actions.First(action => action.Name.Equals(actionName));
+ Assert.NotNull(logicAppAction);
+
+ return logicAppAction;
+ }
+ }
+}
diff --git a/src/Invictus.Testing.Tests.Integration/LogicAppConverterTests.cs b/src/Invictus.Testing.Tests.Integration/LogicAppConverterTests.cs
new file mode 100644
index 0000000..39583d7
--- /dev/null
+++ b/src/Invictus.Testing.Tests.Integration/LogicAppConverterTests.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Bogus;
+using Bogus.Extensions;
+using Invictus.Testing.Model;
+using Microsoft.Azure.Management.Logic.Models;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Invictus.Testing.Tests.Integration
+{
+ public class LogicAppConverterTests
+ {
+ private static readonly Faker BogusGenerator = new Faker();
+
+ [Fact]
+ public void ToLogicApp_WithWorkflow_CreatesAlternative()
+ {
+ // Arrange
+ string state = BogusGenerator.PickRandom("NotSpecified", "Completed", "Enabled", "Disabled", "Deleted", "Suspended");
+ var workflow = new Workflow(
+ name: BogusGenerator.Internet.DomainName().OrNull(BogusGenerator),
+ createdTime: BogusGenerator.Date.Recent(),
+ changedTime: BogusGenerator.Date.Recent(),
+ state: state.OrNull(BogusGenerator),
+ version: BogusGenerator.System.Version().ToString().OrNull(BogusGenerator),
+ accessEndpoint: BogusGenerator.Internet.IpAddress().ToString().OrNull(BogusGenerator),
+ definition: BogusGenerator.Random.String().OrNull(BogusGenerator));
+
+ // Act
+ var actual = LogicAppConverter.ToLogicApp(workflow);
+
+ // Assert
+ Assert.NotNull(actual);
+ Assert.Equal(workflow.Name, actual.Name);
+ Assert.Equal(workflow.CreatedTime, actual.CreatedTime);
+ Assert.Equal(workflow.ChangedTime, actual.ChangedTime);
+ Assert.Equal(workflow.State, actual.State);
+ Assert.Equal(workflow.Version, actual.Version);
+ Assert.Equal(workflow.AccessEndpoint, actual.AccessEndpoint);
+ Assert.Equal(workflow.Definition, actual.Definition);
+ }
+
+ [Fact]
+ public void ToLogicAppAction_WithInputOutput_CreatesAlternative()
+ {
+ // Arrange
+ var trackedProperties = new Dictionary
+ {
+ [Guid.NewGuid().ToString()] = BogusGenerator.Random.Word()
+ };
+
+ string trackedPropertiesJson = JsonConvert.SerializeObject(trackedProperties).OrNull(BogusGenerator);
+
+ var workflowAction = new WorkflowRunAction(
+ name: BogusGenerator.Internet.DomainName().OrNull(BogusGenerator),
+ startTime: BogusGenerator.Date.Past(),
+ endTime: BogusGenerator.Date.Past(),
+ status: GenerateStatus(),
+ error: BogusGenerator.Random.Bytes(10),
+ trackedProperties: trackedPropertiesJson);
+ var inputs = BogusGenerator.Random.String();
+ var outputs = BogusGenerator.Random.String();
+
+ // Act
+ var actual = LogicAppConverter.ToLogicAppAction(workflowAction, inputs, outputs);
+
+ // Assert
+ Assert.NotNull(actual);
+ Assert.Equal(workflowAction.Name, actual.Name);
+ Assert.Equal(workflowAction.StartTime, actual.StartTime);
+ Assert.Equal(workflowAction.EndTime, actual.EndTime);
+ Assert.Equal(workflowAction.Status, actual.Status);
+ Assert.Equal(workflowAction.Error, actual.Error);
+ Assert.Equal(inputs, actual.Inputs);
+ Assert.Equal(outputs, actual.Outputs);
+ Assert.True(trackedPropertiesJson == null || trackedProperties.SequenceEqual(actual.TrackedProperties));
+ }
+
+ [Fact]
+ public void ToLogicAppRun_WithWorkflowRunAndActions_CreatesCombinedModel()
+ {
+ // Arrange
+ WorkflowRunTrigger trigger = CreateWorkflowRunTrigger();
+ WorkflowRun workflowRun = CreateWorkflowRun(trigger);
+ IEnumerable actions = CreateLogicAppActions();
+
+ // Act
+ var actual = LogicAppConverter.ToLogicAppRun(workflowRun, actions);
+
+ // Assert
+ Assert.NotNull(actual);
+ Assert.Equal(workflowRun.Name, actual.Id);
+ Assert.Equal(workflowRun.Status, actual.Status);
+ Assert.Equal(workflowRun.StartTime, actual.StartTime);
+ Assert.Equal(workflowRun.EndTime, actual.EndTime);
+ Assert.Equal(workflowRun.Error, actual.Error);
+ Assert.Equal(workflowRun.Correlation?.ClientTrackingId, actual.CorrelationId);
+ Assert.Equal(actions, actual.Actions);
+
+ Assert.Equal(trigger.Name, actual.Trigger.Name);
+ Assert.Equal(trigger.Inputs, actual.Trigger.Inputs);
+ Assert.Equal(trigger.Outputs, actual.Trigger.Outputs);
+ Assert.Equal(trigger.StartTime, actual.Trigger.StartTime);
+ Assert.Equal(trigger.EndTime, actual.Trigger.EndTime);
+ Assert.Equal(trigger.Status, actual.Trigger.Status);
+ Assert.Equal(trigger.Error, actual.Trigger.Error);
+
+ Assert.All(actions.Where(action => action.TrackedProperties != null), action =>
+ {
+ Assert.All(action.TrackedProperties, prop =>
+ {
+ Assert.Contains(prop, actual.TrackedProperties);
+ });
+ });
+ }
+
+ private static WorkflowRunTrigger CreateWorkflowRunTrigger()
+ {
+ var trigger = new WorkflowRunTrigger(
+ name: BogusGenerator.Internet.DomainName().OrNull(BogusGenerator),
+ inputs: BogusGenerator.Random.Word().OrNull(BogusGenerator),
+ outputs: BogusGenerator.Random.Word().OrNull(BogusGenerator),
+ startTime: BogusGenerator.Date.Past(),
+ endTime: BogusGenerator.Date.Recent(),
+ status: GenerateStatus(),
+ error: BogusGenerator.Random.Bytes(10).OrNull(BogusGenerator));
+ return trigger;
+ }
+
+ private static WorkflowRun CreateWorkflowRun(WorkflowRunTrigger trigger)
+ {
+ var correlation = new Correlation(BogusGenerator.Random.String().OrNull(BogusGenerator)).OrNull(BogusGenerator);
+ var workflowRun = new WorkflowRun(
+ name: BogusGenerator.Internet.DomainWord().OrNull(BogusGenerator),
+ startTime: BogusGenerator.Date.Recent(),
+ status: GenerateStatus(),
+ error: BogusGenerator.Random.Bytes(10).OrNull(BogusGenerator),
+ correlation: correlation,
+ trigger: trigger);
+ return workflowRun;
+ }
+
+ private static IEnumerable CreateLogicAppActions()
+ {
+ int actionCount = BogusGenerator.Random.Int(1, 10);
+ int propertyCount = BogusGenerator.Random.Int(1, 10);
+
+ Dictionary trackedProperties =
+ BogusGenerator.Make(propertyCount, () => new KeyValuePair(Guid.NewGuid().ToString(), BogusGenerator.Random.Word()))
+ .ToDictionary(item => item.Key, item => item.Value);
+
+ IList actions = BogusGenerator.Make(actionCount, () =>
+ {
+ return new LogicAppAction
+ {
+ Name = BogusGenerator.Internet.DomainWord().OrNull(BogusGenerator),
+ Inputs = BogusGenerator.Random.Words().OrNull(BogusGenerator),
+ Outputs = BogusGenerator.Random.Words().OrNull(BogusGenerator),
+ Status = GenerateStatus(),
+ StartTime = BogusGenerator.Date.Past(),
+ EndTime = BogusGenerator.Date.Recent(),
+ Error = BogusGenerator.Random.Byte(10).OrNull(BogusGenerator),
+ TrackedProperties = trackedProperties
+ };
+ });
+
+ return actions;
+ }
+
+ private static string GenerateStatus()
+ {
+ return BogusGenerator.PickRandom("NotSpecified", "Paused", "Running",
+ "Waiting", "Succeeded", "Skipped", "Suspended", "Cancelled", "Failed", "Faulted",
+ "TimedOut", "Aborted", "Ignored");
+ }
+ }
+}
diff --git a/src/Invictus.Testing.Tests.Integration/LogicAppExceptionTests.cs b/src/Invictus.Testing.Tests.Integration/LogicAppExceptionTests.cs
new file mode 100644
index 0000000..38a1d51
--- /dev/null
+++ b/src/Invictus.Testing.Tests.Integration/LogicAppExceptionTests.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Invictus.Testing.Tests.Integration
+{
+ public class LogicAppExceptionTests
+ {
+ [Fact]
+ public void CreateException_WithAllProperties_AssignsAllProperties()
+ {
+ // Arrange
+ string logicApp = "logic app name", resourceGroup = "resource group", subscriptionId = "subscription ID";
+ string message = "There's something wrong with the logic app";
+ var innerException = new KeyNotFoundException("Couldn't find the logic app");
+
+ // Act
+ var exception = new LogicAppException(subscriptionId, resourceGroup, logicApp, message, innerException);
+
+ // Assert
+ Assert.Equal(message, exception.Message);
+ Assert.Equal(logicApp, exception.LogicAppName);
+ Assert.Equal(resourceGroup, exception.ResourceGroup);
+ Assert.Equal(subscriptionId, exception.SubscriptionId);
+ Assert.Equal(innerException, exception.InnerException);
+ }
+ }
+}
diff --git a/src/Invictus.Testing.Tests.Integration/LogicAppNotUpdatedExceptionTests.cs b/src/Invictus.Testing.Tests.Integration/LogicAppNotUpdatedExceptionTests.cs
new file mode 100644
index 0000000..94cebb0
--- /dev/null
+++ b/src/Invictus.Testing.Tests.Integration/LogicAppNotUpdatedExceptionTests.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using Xunit;
+
+namespace Invictus.Testing.Tests.Integration
+{
+ public class LogicAppNotUpdatedExceptionTests
+ {
+ [Fact]
+ public void CreateException_WithAllProperties_AssignsAllProperties()
+ {
+ // Arrange
+ string logicApp = "logic app name", resourceGroup = "resource group", subscriptionId = "subscription ID";
+ string message = "There's something wrong with updating the logic app";
+ var innerException = new KeyNotFoundException("Couldn't find the logic app");
+
+ // Act
+ var exception = new LogicAppNotUpdatedException(subscriptionId, resourceGroup, logicApp, message, innerException);
+
+ // Assert
+ Assert.Equal(message, exception.Message);
+ Assert.Equal(logicApp, exception.LogicAppName);
+ Assert.Equal(resourceGroup, exception.ResourceGroup);
+ Assert.Equal(subscriptionId, exception.SubscriptionId);
+ Assert.Equal(innerException, exception.InnerException);
+ }
+
+ [Fact]
+ public void SerializeException_WithoutProperties_SerializesWithoutProperties()
+ {
+ // Arrange
+ var innerException = new InvalidOperationException("Problem with update");
+ var exception = new LogicAppNotUpdatedException("App not updated", innerException);
+
+ var expected = exception.ToString();
+
+ // Act
+ LogicAppNotUpdatedException actual = SerializeDeserializeException(exception);
+
+ // Assert
+ Assert.Equal(expected, actual.ToString());
+ }
+
+ [Fact]
+ public void SerializeException_WithProperties_SerializeWithProperties()
+ {
+ // Arrange
+ string logicApp = "logic app name",
+ resourceGroup = "resouce group",
+ subscriptionId = "subscription ID";
+
+ var innerException = new KeyNotFoundException("Problem with update");
+ var exception = new LogicAppNotUpdatedException(subscriptionId, resourceGroup, logicApp, "App not updated", innerException);
+
+ string expected = exception.ToString();
+
+ // Act
+ LogicAppNotUpdatedException actual = SerializeDeserializeException(exception);
+
+ // Assert
+ Assert.Equal(expected, actual.ToString());
+ Assert.Equal(logicApp, actual.LogicAppName);
+ Assert.Equal(resourceGroup, actual.ResourceGroup);
+ Assert.Equal(subscriptionId, actual.SubscriptionId);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Constructor_WithBlankLogicAppName_Fails(string logicAppName)
+ {
+ Assert.Throws(
+ () => new LogicAppNotUpdatedException("subscription ID", "resource group", logicAppName, "App not updated"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Constructor_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ Assert.Throws(
+ () => new LogicAppNotUpdatedException("subscription ID", resourceGroup, "logic app", "App not updated"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Constructor_WithBlankSubscriptionId_Fails(string subscriptionId)
+ {
+ Assert.Throws(
+ () => new LogicAppNotUpdatedException(subscriptionId, "resource group", "logic app", "App not updated"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void ConstructorInnerException_WithBlankLogicAppName_Fails(string logicAppName)
+ {
+ var innerException = new Exception("The cause of the exception");
+ Assert.Throws(
+ () => new LogicAppNotUpdatedException("subscription ID", "resource group", logicAppName, "App not updated", innerException));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void ConstructorInnerException_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ var innerException = new Exception("The cause of the exception");
+ Assert.Throws(
+ () => new LogicAppNotUpdatedException("subscription ID", resourceGroup, "logic app", "App not updated", innerException));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void ConstructorInnerException_WithBlankSubscriptionId_Fails(string subscriptionId)
+ {
+ var innerException = new Exception("The cause of the exception");
+ Assert.Throws(
+ () => new LogicAppNotUpdatedException(subscriptionId, "resource group", "logic app", "Trigger could not be found", innerException));
+ }
+
+ private static LogicAppNotUpdatedException SerializeDeserializeException(LogicAppNotUpdatedException exception)
+ {
+ var formatter = new BinaryFormatter();
+ using (var contents = new MemoryStream())
+ {
+ formatter.Serialize(contents, exception);
+ contents.Seek(0, 0);
+
+ var deserialized = (LogicAppNotUpdatedException) formatter.Deserialize(contents);
+ return deserialized;
+ }
+ }
+ }
+}
diff --git a/src/Invictus.Testing.Tests.Integration/LogicAppTriggerNotFoundExceptionTests.cs b/src/Invictus.Testing.Tests.Integration/LogicAppTriggerNotFoundExceptionTests.cs
new file mode 100644
index 0000000..44fdcfe
--- /dev/null
+++ b/src/Invictus.Testing.Tests.Integration/LogicAppTriggerNotFoundExceptionTests.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using Xunit;
+
+namespace Invictus.Testing.Tests.Integration
+{
+ public class LogicAppTriggerNotFoundExceptionTests
+ {
+ [Fact]
+ public void CreateException_WithAllProperties_AssignsAllProperties()
+ {
+ // Arrange
+ string logicApp = "logic app name", resourceGroup = "resource group", subscriptionId = "subscription ID";
+ string message = "There's something wrong with finding the trigger in the logic app";
+ var innerException = new KeyNotFoundException("Couldn't find the trigger in the logic app");
+
+ // Act
+ var exception = new LogicAppTriggerNotFoundException(subscriptionId, resourceGroup, logicApp, message, innerException);
+
+ // Assert
+ Assert.Equal(message, exception.Message);
+ Assert.Equal(logicApp, exception.LogicAppName);
+ Assert.Equal(resourceGroup, exception.ResourceGroup);
+ Assert.Equal(subscriptionId, exception.SubscriptionId);
+ Assert.Equal(innerException, exception.InnerException);
+ }
+
+ [Fact]
+ public void SerializeException_WithoutProperties_SerializesWithoutProperties()
+ {
+ // Arrange
+ var innerException = new KeyNotFoundException("No trigger with this key found");
+ var exception = new LogicAppTriggerNotFoundException("Trigger could not be found", innerException);
+
+ var expected = exception.ToString();
+
+ // Act
+ LogicAppTriggerNotFoundException actual = SerializeDeserializeException(exception);
+
+ // Assert
+ Assert.Equal(expected, actual.ToString());
+ }
+
+ [Fact]
+ public void SerializeException_WithProperties_SerializeWithProperties()
+ {
+ // Arrange
+ string logicApp = "logic app name",
+ resourceGroup = "resource group",
+ subscriptionId = "subscription ID";
+
+ var innerException = new KeyNotFoundException("No trigger with this key found");
+ var exception = new LogicAppTriggerNotFoundException(subscriptionId, resourceGroup, logicApp, "Trigger could not be found", innerException);
+
+ string expected = exception.ToString();
+
+ // Act
+ LogicAppTriggerNotFoundException actual = SerializeDeserializeException(exception);
+
+ // Assert
+ Assert.Equal(expected, actual.ToString());
+ Assert.Equal(logicApp, actual.LogicAppName);
+ Assert.Equal(resourceGroup, actual.ResourceGroup);
+ Assert.Equal(subscriptionId, actual.SubscriptionId);
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Constructor_WithBlankLogicAppName_Fails(string logicAppName)
+ {
+ Assert.Throws(
+ () => new LogicAppTriggerNotFoundException("subscription ID", "resource group", logicAppName, "Trigger could not be found"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Constructor_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ Assert.Throws(
+ () => new LogicAppTriggerNotFoundException("subscription ID", resourceGroup, "logic app", "Trigger could not be found"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Constructor_WithBlankSubscriptionId_Fails(string subscriptionId)
+ {
+ Assert.Throws(
+ () => new LogicAppTriggerNotFoundException(subscriptionId, "resource group", "logic app", "Trigger could not be found"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void ConstructorInnerException_WithBlankLogicAppName_Fails(string logicAppName)
+ {
+ var innerException = new Exception("The cause of the exception");
+ Assert.Throws(
+ () => new LogicAppTriggerNotFoundException("subscription ID", "resource group", logicAppName, "Trigger could not be found", innerException));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void ConstructorInnerException_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ var innerException = new Exception("The cause of the exception");
+ Assert.Throws(
+ () => new LogicAppTriggerNotFoundException("subscription ID", resourceGroup, "logic app", "Trigger could not be found", innerException));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void ConstructorInnerException_WithBlankSubscriptionId_Fails(string subscriptionId)
+ {
+ var innerException = new Exception("The cause of the exception");
+ Assert.Throws(
+ () => new LogicAppTriggerNotFoundException(subscriptionId, "resource group", "logic app", "Trigger could not be found", innerException));
+ }
+
+ private static LogicAppTriggerNotFoundException SerializeDeserializeException(LogicAppTriggerNotFoundException exception)
+ {
+ var formatter = new BinaryFormatter();
+ using (var contents = new MemoryStream())
+ {
+ formatter.Serialize(contents, exception);
+ contents.Seek(0, 0);
+
+ var deserialized = (LogicAppTriggerNotFoundException) formatter.Deserialize(contents);
+ return deserialized;
+ }
+ }
+ }
+}
diff --git a/src/Invictus.Testing.Tests.Integration/LogicAppsHelperTests.cs b/src/Invictus.Testing.Tests.Integration/LogicAppsHelperTests.cs
deleted file mode 100644
index 0083468..0000000
--- a/src/Invictus.Testing.Tests.Integration/LogicAppsHelperTests.cs
+++ /dev/null
@@ -1,475 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Invictus.Testing.Model;
-using Invictus.Testing.Serialization;
-using Newtonsoft.Json.Linq;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Invictus.Testing.Tests.Integration
-{
- public class LogicAppsHelperTests : IDisposable
- {
- private readonly ITestOutputHelper _outputWriter;
- private readonly string _resourceGroup, _logicAppName, _logicAppMockingName;
- private readonly LogicAppsHelper _logicAppsHelper;
-
- private static readonly TestConfig Configuration = TestConfig.Create();
-
- ///
- /// Initializes a new instance of the class.
- ///
- public LogicAppsHelperTests(ITestOutputHelper outputWriter)
- {
- _outputWriter = outputWriter;
-
- _resourceGroup = Configuration.GetAzureResourceGroup();
- _logicAppName = Configuration.GetTestLogicAppName();
- _logicAppMockingName = Configuration.GetTestMockingLogicAppName();
-
- string subscriptionId = Configuration.GetAzureSubscriptionId();
- string tenantId = Configuration.GetAzureTenantId();
- string clientId = Configuration.GetAzureClientId();
- string clientSecret = Configuration.GetAzureClientSecret();
- _logicAppsHelper = new LogicAppsHelper(subscriptionId, tenantId, clientId, clientSecret);
- }
-
- [Fact]
- public async Task GetLogicAppTriggerUrl_Success()
- {
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- Assert.NotNull(logicAppTriggerUrl.Value);
- Assert.Equal("POST", logicAppTriggerUrl.Method);
- }
-
- [Fact]
- public async Task GetLogicAppTriggerUrl_ByName_Success()
- {
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl =
- await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName, triggerName: "manual");
-
- // Assert
- Assert.NotNull(logicAppTriggerUrl.Value);
- Assert.Equal("POST", logicAppTriggerUrl.Method);
- }
-
- [Fact]
- public async Task PollForLogicAppRun_ByCorrelationId_Success()
- {
- // Arrange
- DateTime startTime = DateTime.UtcNow;
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var headers = new Dictionary
- {
- { "correlationId", correlationId }
- };
-
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- Task pollingTask = _logicAppsHelper.PollForLogicAppRunAsync(_resourceGroup, _logicAppName, startTime, correlationId);
- Task postTask = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
-
- await Task.WhenAll(pollingTask, postTask);
-
- Assert.NotNull(pollingTask.Result);
- Assert.Equal(correlationId, pollingTask.Result.CorrelationId);
- }
-
- [Fact]
- public async Task PollForLogicAppRuns_ByCorrelationId_AfterTimeoutPeriod_Success()
- {
- // Arrange
- TimeSpan timeout = TimeSpan.FromSeconds(5);
- DateTime startTime = DateTime.UtcNow;
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var headers = new Dictionary
- {
- { "correlationId", correlationId }
- };
-
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- // Poll for all logic app runs with provided correlation id after timeout period expires.
- Task> pollingTask =
- _logicAppsHelper.PollForLogicAppRunsAsync(_resourceGroup, _logicAppName, startTime, correlationId, timeout);
-
- // Run logic app twice with the same correlation id.
- Task postTask1 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
- Task postTask2 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
-
- await Task.WhenAll(pollingTask, postTask1, postTask2);
-
- Assert.NotNull(pollingTask.Result);
- Assert.Equal(2, pollingTask.Result.Count);
- Assert.All(pollingTask.Result, logicAppRun =>
- {
- Assert.Equal(correlationId, logicAppRun.CorrelationId);
- });
- }
-
- [Fact]
- public async Task PollForLogicAppRuns_ByCorrelationId_NumberOfRuns_Success()
- {
- // Arrange
- const int numberOfRuns = 2;
- TimeSpan timeout = TimeSpan.FromSeconds(30);
- DateTime startTime = DateTime.UtcNow;
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var headers = new Dictionary
- {
- { "correlationId", correlationId }
- };
-
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- // Poll for a specific number of logic app runs with provided correlation id.
- Task> pollingTask =
- _logicAppsHelper.PollForLogicAppRunsAsync(_resourceGroup, _logicAppName, startTime, correlationId, timeout, numberOfRuns);
-
- // Run logic app twice with the same correlation id.
- Task postTask1 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
- Task postTask2 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
- await Task.WhenAll(pollingTask, postTask1, postTask2);
-
- Assert.Equal(numberOfRuns, pollingTask.Result.Count);
- Assert.All(pollingTask.Result, logicAppRun =>
- {
- Assert.Equal(correlationId, logicAppRun.CorrelationId);
- });
- }
-
- [Fact]
- public async Task PollForLogicAppRun_ByTrackedProperty_Success()
- {
- // Arrange
- const string trackedPropertyName = "trackedproperty";
- DateTime startTime = DateTime.UtcNow;
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- string trackedPropertyValue = $"tracked-{Guid.NewGuid()}";
-
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- { "trackedpropertyheader1", trackedPropertyValue },
- { "trackedpropertyheader2", trackedPropertyValue }
- };
-
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- Task pollingTask =
- _logicAppsHelper.PollForLogicAppRunAsync(_resourceGroup, _logicAppName, startTime, trackedPropertyName, trackedPropertyValue);
-
- Task postTask = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
-
- await Task.WhenAll(pollingTask, postTask);
-
- Assert.NotNull(pollingTask.Result);
- Assert.True(pollingTask.Result.TrackedProperties.ContainsValue(trackedPropertyValue));
- }
-
- [Fact]
- public async Task PollForLogicAppRun_ByTrackedProperty_DifferentValues_GetsLatest_Success()
- {
- // Arrange
- const string trackedPropertyName = "trackedproperty";
- DateTime startTime = DateTime.UtcNow;
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- string trackedPropertyValue1 = $"tracked-{Guid.NewGuid()}";
- string trackedPropertyValue2 = $"tracked-{Guid.NewGuid()}";
-
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- { "trackedpropertyheader1", trackedPropertyValue1 },
- { "trackedpropertyheader2", trackedPropertyValue2 }
- };
-
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- Task pollingTask =
- _logicAppsHelper.PollForLogicAppRunAsync(_resourceGroup, _logicAppName, startTime, trackedPropertyName, trackedPropertyValue1);
-
- Task postTask = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
-
- await Task.WhenAll(pollingTask, postTask);
-
- Assert.NotNull(pollingTask.Result);
- Assert.True(pollingTask.Result.TrackedProperties.ContainsValue(trackedPropertyValue2));
- }
-
- [Fact]
- public async Task PollForLogicAppRuns_ByTrackedProperty_AfterTimeoutPeriod_Success()
- {
- // Arrange
- const string trackedPropertyName = "trackedproperty";
- DateTime startTime = DateTime.UtcNow;
- TimeSpan timeout = TimeSpan.FromSeconds(5);
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- string trackedPropertyValue = $"tracked-{Guid.NewGuid()}";
-
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- { "trackedpropertyheader1", trackedPropertyValue },
- { "trackedpropertyheader2", trackedPropertyValue }
- };
-
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- // Poll for all logic app runs with provided tracked property after timeout period expires.
- Task> pollingTask =
- _logicAppsHelper.PollForLogicAppRunsAsync(_resourceGroup, _logicAppName, startTime, trackedPropertyName, trackedPropertyValue, timeout);
-
- // Run logic app twice with the same tracked property.
- Task postTask1 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
- Task postTask2 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
-
- await Task.WhenAll(pollingTask, postTask1, postTask2);
-
- Assert.NotNull(pollingTask.Result);
- Assert.Equal(2, pollingTask.Result.Count);
- Assert.All(pollingTask.Result, logicAppRun =>
- {
- Assert.True(logicAppRun.TrackedProperties.ContainsValue(trackedPropertyValue));
- });
- }
-
- [Fact]
- public async Task PollForLogicAppRuns_ByTrackedProperty_NumberOfRuns_Success()
- {
- // Arrange
- const string trackedPropertyName = "trackedproperty";
- const int numberOfRuns = 2;
- DateTime startTime = DateTime.UtcNow;
- TimeSpan timeout = TimeSpan.FromSeconds(30);
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var trackedPropertyValue = Guid.NewGuid().ToString();
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- { "trackedpropertyheader1", trackedPropertyValue },
- { "trackedpropertyheader2", trackedPropertyValue }
- };
-
- // Act
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppName);
-
- // Assert
- // Poll for a specific number of logic app runs with provided tracked property.
- Task> pollingTask =
- _logicAppsHelper.PollForLogicAppRunsAsync(_resourceGroup, _logicAppName, startTime, trackedPropertyName, trackedPropertyValue, timeout, numberOfRuns);
-
- // Run logic app twice with the same tracked property.
- Task postTask1 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
- Task postTask2 = PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
-
- await Task.WhenAll(pollingTask, postTask1, postTask2);
-
- Assert.NotNull(pollingTask.Result);
- Assert.Equal(numberOfRuns, pollingTask.Result.Count);
- Assert.All(pollingTask.Result, logicAppRun =>
- {
- Assert.True(logicAppRun.TrackedProperties.ContainsValue(trackedPropertyValue));
- });
- }
-
- [Fact]
- public async Task EnableStaticResultForAction_Success()
- {
- // Arrange
- const string actionName = "HTTP";
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- };
-
- var staticResultDefinition = new StaticResultDefinition
- {
- Outputs = new Outputs
- {
- Headers = new Dictionary { { "testheader", "testvalue" } },
- StatusCode = "200",
- Body = JToken.Parse("{id : 12345, name : 'test body'}")
- },
- Status = "Succeeded"
- };
-
- // Act
- bool result = await _logicAppsHelper.EnableStaticResultForActionAsync(_resourceGroup, _logicAppMockingName, actionName, staticResultDefinition);
-
- // Assert
- Assert.True(result);
-
- await RunLogicAppOnTriggerUrlAsync(headers);
- LogicAppAction logicAppAction = await PollForLogicAppActionAsync(correlationId, actionName);
-
- Assert.Equal("200", logicAppAction.Outputs.statusCode.ToString());
- Assert.Equal("testvalue", logicAppAction.Outputs.headers["testheader"].ToString());
- Assert.True(logicAppAction.Outputs.body.ToString().Contains("test body"));
- }
-
- [Fact]
- public async Task EnableStaticResultForActions_Success()
- {
- // Arrange
- const string actionName = "HTTP";
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- };
-
- var staticResultDefinition = new StaticResultDefinition
- {
- Outputs = new Outputs
- {
- Headers = new Dictionary { { "testheader", "testvalue" } },
- StatusCode = "200",
- Body = "test body"
- },
- Status = "Succeeded"
- };
-
- var actions = new Dictionary { { actionName, staticResultDefinition } };
-
- // Act
- bool isSuccess = await _logicAppsHelper.EnableStaticResultForActionsAsync(_resourceGroup, _logicAppMockingName, actions);
-
- // Assert
- Assert.True(isSuccess);
-
- await RunLogicAppOnTriggerUrlAsync(headers);
- LogicAppAction logicAppAction = await PollForLogicAppActionAsync(correlationId, actionName);
-
- Assert.Equal("200", logicAppAction.Outputs.statusCode.ToString());
- Assert.Equal("testvalue", logicAppAction.Outputs.headers["testheader"].ToString());
- Assert.Equal("test body", logicAppAction.Outputs.body.ToString());
- }
-
- [Fact]
- public async Task DisableStaticResultForAction_Success()
- {
- // Arrange
- const string actionName = "HTTP";
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- };
-
- // Act
- bool isSuccess = await _logicAppsHelper.DisableStaticResultForActionAsync(_resourceGroup, _logicAppMockingName, actionName);
-
- // Assert
- Assert.True(isSuccess);
-
- await RunLogicAppOnTriggerUrlAsync(headers);
- LogicAppAction logicAppAction = await PollForLogicAppActionAsync(correlationId, actionName);
-
- string body = logicAppAction.Outputs.body;
- Assert.NotEqual("test body", body);
- }
-
- [Fact]
- public async Task DisableStaticResultForAllActions_Success()
- {
- // Arrange
- DateTime startTime = DateTime.UtcNow.AddMinutes(-1);
-
- string correlationId = $"correlationId-{Guid.NewGuid()}";
- var headers = new Dictionary
- {
- { "correlationId", correlationId },
- };
-
- // Act
- bool isSuccess = await _logicAppsHelper.DisableAllStaticResultsForLogicAppAsync(_resourceGroup, _logicAppMockingName);
-
- // Assert
- Assert.True(isSuccess);
-
- await RunLogicAppOnTriggerUrlAsync(headers);
-
- // Check logic app run for static result
- LogicAppRun logicAppRun = await _logicAppsHelper.PollForLogicAppRunAsync(_resourceGroup, _logicAppMockingName, startTime, correlationId);
- Assert.All(logicAppRun.Actions, action =>
- {
- string body = action.Outputs.body;
- Assert.NotEqual("test body", body);
- });
- }
-
- private async Task RunLogicAppOnTriggerUrlAsync(IDictionary headers)
- {
- LogicAppTriggerUrl logicAppTriggerUrl = await _logicAppsHelper.GetLogicAppTriggerUrlAsync(_resourceGroup, _logicAppMockingName);
- await PostHeadersToLogicAppTriggerAsync(logicAppTriggerUrl.Value, headers);
- }
-
- private async Task PollForLogicAppActionAsync(string correlationId, string actionName)
- {
- DateTime startTime = DateTime.UtcNow.AddMinutes(-1);
-
- LogicAppRun logicAppRun = await _logicAppsHelper.PollForLogicAppRunAsync(_resourceGroup, _logicAppMockingName, startTime, correlationId);
- Assert.True(logicAppRun.Actions.Count != 0);
-
- LogicAppAction logicAppAction = logicAppRun.Actions.First(action => action.Name.Equals(actionName));
- Assert.NotNull(logicAppAction);
-
- return logicAppAction;
- }
-
- private static async Task PostHeadersToLogicAppTriggerAsync(string uri, IDictionary headers)
- {
- using (var request = new HttpRequestMessage(HttpMethod.Post, uri))
- {
- foreach ((string name, string value) in headers)
- {
- request.Headers.Add(name, value);
- }
-
- using (var client = new HttpClient())
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- }
- }
- }
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- public void Dispose()
- {
- _logicAppsHelper?.Dispose();
- }
- }
-}
diff --git a/src/Invictus.Testing.Tests.Integration/LogicAppsProviderTests.cs b/src/Invictus.Testing.Tests.Integration/LogicAppsProviderTests.cs
new file mode 100644
index 0000000..1771607
--- /dev/null
+++ b/src/Invictus.Testing.Tests.Integration/LogicAppsProviderTests.cs
@@ -0,0 +1,308 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Invictus.Testing.Model;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Invictus.Testing.Tests.Integration
+{
+ public class LogicAppsProviderTests : IntegrationTest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LogicAppsProviderTests(ITestOutputHelper outputWriter) : base(outputWriter)
+ {
+ }
+
+ [Fact]
+ public async Task PollForLogicAppRun_WithoutLogger_Success()
+ {
+ // Arrange
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId }
+ };
+
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication, Logger))
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ Task postTask1 = logicApp.TriggerAsync(headers);
+ Task postTask2 = logicApp.TriggerAsync(headers);
+
+ // Act
+ Task> pollingTask =
+ LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, Authentication)
+ .WithCorrelationId(correlationId)
+ .PollForLogicAppRunsAsync();
+
+ await Task.WhenAll(pollingTask, postTask1, postTask2);
+
+ // Assert
+ Assert.NotNull(pollingTask.Result);
+ Assert.NotEmpty(pollingTask.Result);
+ Assert.All(pollingTask.Result, logicAppRun =>
+ {
+ Assert.Equal(correlationId, logicAppRun.CorrelationId);
+ });
+ }
+ }
+
+ [Fact]
+ public async Task PollForLogicAppRun_NotMatchedCorrelation_Fails()
+ {
+ // Arrange
+ var headers = new Dictionary
+ {
+ { "correlationId", $"correlationId-{Guid.NewGuid()}" }
+ };
+
+ // Act
+ Task pollingTask =
+ LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, Authentication, Logger)
+ .WithTimeout(TimeSpan.FromSeconds(5))
+ .WithCorrelationId("not-matched-correlation-ID")
+ .PollForSingleLogicAppRunAsync();
+
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication, Logger))
+ {
+ // Assert
+ await logicApp.TriggerAsync(headers);
+ await Assert.ThrowsAsync(() => pollingTask);
+ }
+ }
+
+ [Fact]
+ public async Task PollForLogicAppRun_ByCorrelationId_Success()
+ {
+ // Arrange
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId }
+ };
+
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication, Logger))
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ Task postTask = logicApp.TriggerAsync(headers);
+
+ // Act
+ Task pollingTask =
+ LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, Authentication, Logger)
+ .WithCorrelationId(correlationId)
+ .PollForSingleLogicAppRunAsync();
+
+ await Task.WhenAll(pollingTask, postTask);
+
+ // Assert
+ Assert.NotNull(pollingTask.Result);
+ Assert.Equal(correlationId, pollingTask.Result.CorrelationId);
+ }
+ }
+
+ [Fact]
+ public async Task PollForLogicAppRuns_ByCorrelationId_NumberOfRuns_Success()
+ {
+ // Arrange
+ const int numberOfRuns = 2;
+ TimeSpan timeout = TimeSpan.FromSeconds(30);
+
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId }
+ };
+
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication))
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ // Run logic app twice with the same correlation id.
+ Task postTask1 = logicApp.TriggerAsync(headers);
+ Task postTask2 = logicApp.TriggerAsync(headers);
+
+ // Act
+ Task> pollingTask =
+ LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, Authentication, Logger)
+ .WithCorrelationId(correlationId)
+ .WithTimeout(timeout)
+ .PollForLogicAppRunsAsync(numberOfRuns);
+
+ await Task.WhenAll(pollingTask, postTask1, postTask2);
+
+ // Assert
+ Assert.Equal(numberOfRuns, pollingTask.Result.Count());
+ Assert.All(pollingTask.Result, logicAppRun =>
+ {
+ Assert.Equal(correlationId, logicAppRun.CorrelationId);
+ });
+ }
+ }
+
+ [Fact]
+ public async Task PollForLogicAppRun_ByTrackedProperty_Success()
+ {
+ // Arrange
+ const string trackedPropertyName = "trackedproperty";
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ string trackedPropertyValue = $"tracked-{Guid.NewGuid()}";
+
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId },
+ { "trackedpropertyheader1", trackedPropertyValue },
+ { "trackedpropertyheader2", trackedPropertyValue }
+ };
+
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication))
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ Task postTask = logicApp.TriggerAsync(headers);
+
+ // Act
+ Task pollingTask =
+ LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, Authentication, Logger)
+ .WithTrackedProperty(trackedPropertyName, trackedPropertyValue)
+ .PollForSingleLogicAppRunAsync();
+
+ await Task.WhenAll(pollingTask, postTask);
+
+ // Assert
+ Assert.NotNull(pollingTask.Result);
+ Assert.Contains(pollingTask.Result.TrackedProperties, property => property.Value == trackedPropertyValue);
+ }
+ }
+
+ [Fact]
+ public async Task PollForLogicAppRun_ByTrackedProperty_DifferentValues_GetsLatest_Success()
+ {
+ // Arrange
+ const string trackedPropertyName = "trackedproperty";
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ string trackedPropertyValue1 = $"tracked-{Guid.NewGuid()}";
+ string trackedPropertyValue2 = $"tracked-{Guid.NewGuid()}";
+
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId },
+ { "trackedpropertyheader1", trackedPropertyValue1 },
+ { "trackedpropertyheader2", trackedPropertyValue2 }
+ };
+
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication))
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ Task postTask = logicApp.TriggerAsync(headers);
+
+ // Act
+ Task pollingTask =
+ LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, Authentication, Logger)
+ .WithTrackedProperty(trackedPropertyName, trackedPropertyValue1)
+ .PollForSingleLogicAppRunAsync();
+
+ await Task.WhenAll(pollingTask, postTask);
+
+ // Assert
+ Assert.NotNull(pollingTask.Result);
+ Assert.Contains(pollingTask.Result.TrackedProperties, property => property.Value == trackedPropertyValue2);
+ }
+ }
+
+ [Fact]
+ public async Task PollForLogicAppRuns_ByTrackedProperty_NumberOfRuns_Success()
+ {
+ // Arrange
+ const string trackedPropertyName = "trackedproperty";
+ const int numberOfRuns = 2;
+ TimeSpan timeout = TimeSpan.FromSeconds(40);
+
+ string correlationId = $"correlationId-{Guid.NewGuid()}";
+ var trackedPropertyValue = Guid.NewGuid().ToString();
+ var headers = new Dictionary
+ {
+ { "correlationId", correlationId },
+ { "trackedpropertyheader1", trackedPropertyValue },
+ { "trackedpropertyheader2", trackedPropertyValue }
+ };
+
+ using (var logicApp = await LogicAppClient.CreateAsync(ResourceGroup, LogicAppName, Authentication))
+ await using (await logicApp.TemporaryEnableAsync())
+ {
+ // Run logic app twice with the same tracked property.
+ Task postTask1 = logicApp.TriggerAsync(headers);
+ Task postTask2 = logicApp.TriggerAsync(headers);
+
+ // Act
+ // Poll for a specific number of logic app runs with provided tracked property.
+ Task> pollingTask =
+ LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, Authentication, Logger)
+ .WithTrackedProperty(trackedPropertyName, trackedPropertyValue)
+ .WithTimeout(timeout)
+ .PollForLogicAppRunsAsync(numberOfRuns);
+
+ await Task.WhenAll(pollingTask, postTask1, postTask2);
+
+ // Assert
+ Assert.NotNull(pollingTask.Result);
+ Assert.Equal(numberOfRuns, pollingTask.Result.Count());
+ Assert.All(pollingTask.Result, logicAppRun =>
+ {
+ Assert.Contains(logicAppRun.TrackedProperties, property => property.Value == trackedPropertyValue);
+ });
+ }
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void Constructor_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ Assert.Throws(
+ () => LogicAppsProvider.LocatedAt(resourceGroup, LogicAppName, Authentication));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void Constructor_WithBlankLogicApp_Fails(string logicApp)
+ {
+ Assert.Throws(
+ () => LogicAppsProvider.LocatedAt(ResourceGroup, logicApp, Authentication));
+ }
+
+ [Fact]
+ public void Constructor_WithoutAuthentication_Fails()
+ {
+ Assert.ThrowsAny(
+ () => LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, authentication: null));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void ConstructorWithLogger_WithBlankResourceGroup_Fails(string resourceGroup)
+ {
+ Assert.Throws(
+ () => LogicAppsProvider.LocatedAt(resourceGroup, LogicAppName, Authentication, Logger));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void ConstructorWithLogger_WithBlankLogicApp_Fails(string logicApp)
+ {
+ Assert.Throws(
+ () => LogicAppsProvider.LocatedAt(ResourceGroup, logicApp, Authentication, Logger));
+ }
+
+ [Fact]
+ public void ConstructorWithLogger_WithoutAuthentication_Fails()
+ {
+ Assert.ThrowsAny(
+ () => LogicAppsProvider.LocatedAt(ResourceGroup, LogicAppName, authentication: null, logger: Logger));
+ }
+ }
+}
diff --git a/src/Invictus.Testing/AsyncDisposable.cs b/src/Invictus.Testing/AsyncDisposable.cs
new file mode 100644
index 0000000..5734e25
--- /dev/null
+++ b/src/Invictus.Testing/AsyncDisposable.cs
@@ -0,0 +1,53 @@
+using System.Threading.Tasks;
+using GuardNet;
+
+// ReSharper disable once CheckNamespace
+namespace System
+{
+ ///
+ /// Represents an abstracted way to define setup/teardown functions in an implementation.
+ ///
+ public class AsyncDisposable : IAsyncDisposable
+ {
+ private readonly Func _teardown;
+
+ private AsyncDisposable(Func teardown)
+ {
+ Guard.NotNull(teardown, nameof(teardown));
+ _teardown = teardown;
+ }
+
+ ///
+ /// Create an instance of the class to simulate setup/teardown actions.
+ ///
+ /// The action to run when the instance is being disposed.
+ public static AsyncDisposable Create(Func teardown)
+ {
+ Guard.NotNull(teardown, nameof(teardown));
+ return new AsyncDisposable(teardown);
+ }
+
+ ///
+ /// Create an instance of the class to simulate setup/teardown actions.
+ ///
+ /// The action to run when the instance is created (now).
+ /// The action to run when the instance is being disposed.
+ public static async Task CreateAsync(Func setup, Func teardown)
+ {
+ Guard.NotNull(setup, nameof(setup));
+ Guard.NotNull(teardown, nameof(teardown));
+
+ await setup();
+ return new AsyncDisposable(teardown);
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
+ ///
+ /// A task that represents the asynchronous dispose operation.
+ public async ValueTask DisposeAsync()
+ {
+ await _teardown();
+ }
+ }
+}
diff --git a/src/Invictus.Testing/Converter.cs b/src/Invictus.Testing/Converter.cs
deleted file mode 100644
index a0317bf..0000000
--- a/src/Invictus.Testing/Converter.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-using Invictus.Testing.Model;
-using Microsoft.Azure.Management.Logic.Models;
-using Newtonsoft.Json.Linq;
-
-namespace Invictus.Testing
-{
- public class Converter
- {
- ///
- /// Convert to LogicAppRun.
- ///
- ///
- ///
- ///
- ///
- ///
- public static LogicAppRun ToLogicAppRun(LogicAppsHelper helper, string resourceGroupName, string logicAppName, WorkflowRun workFlowRun)
- {
- var logicAppRun = (LogicAppRun)workFlowRun;
-
- logicAppRun.Actions = helper.GetLogicAppRunActionsAsync(resourceGroupName, logicAppName, workFlowRun.Name, false).Result;
-
- logicAppRun.TrackedProperties = GetAllTrackedProperties(logicAppRun.Actions);
-
- return logicAppRun;
- }
-
- ///
- /// Convert to LogicAppRun.
- ///
- ///
- ///
- ///
- public static LogicAppRun ToLogicAppRun(WorkflowRun workFlowRun, List actions)
- {
- var logicAppRun = (LogicAppRun)workFlowRun;
-
- logicAppRun.Actions = actions;
- logicAppRun.TrackedProperties = GetAllTrackedProperties(logicAppRun.Actions);
-
- return logicAppRun;
- }
-
- ///
- /// Convert to LogicAppAction.
- ///
- ///
- ///
- public static async Task ToLogicAppActionAsync(WorkflowRunAction workflowRunAction)
- {
- var logicAppAction = (LogicAppAction)workflowRunAction;
-
- if (workflowRunAction.InputsLink != null)
- {
- logicAppAction.Inputs = JToken.Parse(await DoHttpRequestAsync(workflowRunAction.InputsLink.Uri));
- }
- if (workflowRunAction.OutputsLink != null)
- {
- logicAppAction.Outputs = JToken.Parse(await DoHttpRequestAsync(workflowRunAction.OutputsLink.Uri));
- }
-
- return logicAppAction;
- }
-
- #region Private Methods
- private static async Task DoHttpRequestAsync(string uri)
- {
- string responseString = string.Empty;
- using (var httpClient = new HttpClient())
- {
- responseString = await httpClient.GetStringAsync(uri);
- }
-
- return responseString;
- }
-
- private static Dictionary GetAllTrackedProperties(List actions)
- {
- return actions
- .Where(x => x.TrackedProperties != null)
- .OrderByDescending(x => x.StartTime)
- .SelectMany(a => a.TrackedProperties)
- .GroupBy(x => x.Key)
- .Select(g => g.First())
- .ToDictionary(x => x.Key, x => x.Value);
- }
- #endregion
- }
-}
diff --git a/src/Invictus.Testing/Invictus.Testing.csproj b/src/Invictus.Testing/Invictus.Testing.csproj
index cd9560b..934cd7d 100644
--- a/src/Invictus.Testing/Invictus.Testing.csproj
+++ b/src/Invictus.Testing/Invictus.Testing.csproj
@@ -17,8 +17,10 @@
+
+
diff --git a/src/Invictus.Testing/LogicAppClient.cs b/src/Invictus.Testing/LogicAppClient.cs
new file mode 100644
index 0000000..d397029
--- /dev/null
+++ b/src/Invictus.Testing/LogicAppClient.cs
@@ -0,0 +1,448 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using GuardNet;
+using Invictus.Testing.Model;
+using Invictus.Testing.Serialization;
+using Microsoft.Azure.Management.Logic;
+using Microsoft.Azure.Management.Logic.Models;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Rest.Azure;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Invictus.Testing
+{
+ ///
+ /// Representing client operations on a given logic app running in Azure.
+ ///
+ public class LogicAppClient : IDisposable
+ {
+ private readonly string _resourceGroup, _logicAppName;
+ private readonly LogicManagementClient _logicManagementClient;
+ private readonly ILogger _logger;
+
+ private static readonly HttpClient HttpClient = new HttpClient();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The logic management client to run REST operations during client operations.
+ /// The instance to write diagnostic trace messages while interacting with the logic app.
+ public LogicAppClient(string resourceGroup, string logicAppName, LogicManagementClient client, ILogger logger)
+ {
+ Guard.NotNullOrEmpty(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrEmpty(logicAppName, nameof(logicAppName));
+ Guard.NotNull(client, nameof(client));
+
+ _resourceGroup = resourceGroup;
+ _logicAppName = logicAppName;
+ _logicManagementClient = client;
+ _logger = logger ?? NullLogger.Instance;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The logic management client to run REST operations during client operations.
+ public LogicAppClient(string resourceGroup, string logicAppName, LogicManagementClient client)
+ : this(resourceGroup, logicAppName, client, NullLogger.Instance)
+ {
+ }
+
+ ///
+ /// Creates a new authenticated instance of the .
+ ///
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The authentication mechanism to authenticate this client.
+ ///
+ /// An authenticated client capable of interacting with the logic app resource running in Azure.
+ ///
+ public static async Task CreateAsync(
+ string resourceGroup,
+ string logicAppName,
+ LogicAuthentication authentication)
+ {
+ Guard.NotNullOrEmpty(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrEmpty(logicAppName, nameof(logicAppName));
+ Guard.NotNull(authentication, nameof(authentication));
+
+ return await CreateAsync(resourceGroup, logicAppName, authentication, NullLogger.Instance);
+ }
+
+ ///
+ /// Creates a new authenticated instance of the .
+ ///
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The authentication mechanism to authenticate this client.
+ /// The instance to write diagnostic trace messages while interacting with the logic app.
+ ///
+ /// An authenticated client capable of interacting with the logic app resource running in Azure.
+ ///
+ public static async Task CreateAsync(
+ string resourceGroup,
+ string logicAppName,
+ LogicAuthentication authentication,
+ ILogger logger)
+ {
+ Guard.NotNullOrEmpty(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrEmpty(logicAppName, nameof(logicAppName));
+ Guard.NotNull(authentication, nameof(authentication));
+
+ LogicManagementClient managementClient = await authentication.AuthenticateAsync();
+ logger = logger ?? NullLogger.Instance;
+ return new LogicAppClient(resourceGroup, logicAppName, managementClient, logger);
+ }
+
+ ///
+ /// Temporary enables the current logic app resource on Azure, and disables the logic app after the returned instance gets disposed.
+ ///
+ ///
+ /// An instance to control the removal of the updates.
+ ///
+ public async Task TemporaryEnableAsync()
+ {
+ return await AsyncDisposable.CreateAsync(
+ async () =>
+ {
+ _logger.LogTrace("Enables (+) the workflow on logic app '{LogicAppName}' in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ await _logicManagementClient.Workflows.EnableAsync(_resourceGroup, _logicAppName);
+ },
+ async () =>
+ {
+ _logger.LogTrace("Disables (-) the workflow on logic app '{LogicAppName}' in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ await _logicManagementClient.Workflows.DisableAsync(_resourceGroup, _logicAppName);
+ });
+ }
+
+ ///
+ /// Updates the current JSON logic app definition with the given ,
+ /// and removes this update after the returned instance gets disposed.
+ ///
+ /// Then JSON representation of the new definition.
+ ///
+ /// An instance to control the removal of the updates.
+ ///
+ public async Task TemporaryUpdateAsync(string logicAppDefinition)
+ {
+ Guard.NotNull(logicAppDefinition, nameof(logicAppDefinition));
+
+ Workflow workflow = await _logicManagementClient.Workflows.GetAsync(_resourceGroup, _logicAppName);
+ object originalAppDefinition = workflow.Definition;
+
+ _logger.LogTrace("Updates (+) the logic app '{LogicAppName}' workflow definition in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ await UpdateAsync(workflow, JObject.Parse(logicAppDefinition));
+ return AsyncDisposable.Create(async () =>
+ {
+ _logger.LogTrace("Reverts (-) the update of the logic app '{LogicAppName}' workflow definition in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ await UpdateAsync(workflow, originalAppDefinition);
+ });
+ }
+
+ private async Task UpdateAsync(Workflow workflow, object logicAppDefinition)
+ {
+ workflow.Definition = logicAppDefinition;
+ Workflow resultWorkflow =
+ await _logicManagementClient.Workflows.CreateOrUpdateAsync(_resourceGroup, _logicAppName, workflow);
+
+ if (resultWorkflow.Name != _logicAppName)
+ {
+ throw new InvalidOperationException(
+ $"Could not update the logic app '{_logicAppName}' workflow in resource group '{_resourceGroup}'correctly, "
+ + "please make sure the authentication on this resource is set correctly and has the right access to this resource");
+ }
+ }
+
+ ///
+ /// Deletes the current logic app resource on Azure.
+ ///
+ public async Task DeleteAsync()
+ {
+ _logger.LogTrace("Deletes the workflow of logic app '{LogicAppName}' in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ await _logicManagementClient.Workflows.DeleteAsync(_resourceGroup, _logicAppName);
+ }
+
+ ///
+ /// Runs the current logic app resource by searching for triggers on the logic app.
+ ///
+ /// When no trigger can be found on the logic app.
+ public async Task RunAsync()
+ {
+ string triggerName = await GetTriggerNameAsync();
+ await RunByNameAsync(triggerName);
+ }
+
+ ///
+ /// Runs the current logic app resource using the given .
+ ///
+ /// The name of the trigger that executes a workflow in the logic app.
+ public async Task RunByNameAsync(string triggerName)
+ {
+ Guard.NotNullOrEmpty(triggerName, nameof(triggerName));
+
+ _logger.LogTrace("Run the workflow trigger of logic app '{LogicAppName}' in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ await _logicManagementClient.WorkflowTriggers.RunAsync(_resourceGroup, _logicAppName, triggerName);
+ }
+
+
+ ///
+ /// Gets the logic app definition information.
+ ///
+ public async Task GetMetadataAsync()
+ {
+ Workflow workflow = await _logicManagementClient.Workflows.GetAsync(_resourceGroup, _logicAppName);
+ return LogicAppConverter.ToLogicApp(workflow);
+ }
+
+ ///
+ /// Run logic app on the current trigger URL, posting the given .
+ ///
+ /// The headers to send with the trigger URL of the current logic app.
+ public async Task TriggerAsync(IDictionary headers)
+ {
+ Guard.NotNull(headers, nameof(headers));
+ Guard.NotAny(headers, nameof(headers));
+
+ LogicAppTriggerUrl triggerUrl = await GetTriggerUrlAsync();
+
+ _logger.LogTrace("Trigger the workflow of logic app '{LogicAppName}' in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ using (var request = new HttpRequestMessage(HttpMethod.Post, triggerUrl.Value))
+ {
+ foreach (KeyValuePair header in headers)
+ {
+ request.Headers.Add(header.Key, header.Value);
+ }
+
+ await HttpClient.SendAsync(request);
+ }
+ }
+
+ ///
+ /// Gets the URL on which the workflow with trigger can be run by searching the workflow for configured triggers.
+ ///
+ /// When no trigger can be found on the logic app.
+ public async Task GetTriggerUrlAsync()
+ {
+ string triggerName = await GetTriggerNameAsync();
+ LogicAppTriggerUrl triggerUrl = await GetTriggerUrlByNameAsync(triggerName);
+
+ return triggerUrl;
+ }
+
+ ///
+ /// Gets the URL on which the workflow with the can be run.
+ ///
+ /// The name of the trigger that relates to a workflow.
+ public async Task GetTriggerUrlByNameAsync(string triggerName)
+ {
+ Guard.NotNullOrEmpty(triggerName, nameof(triggerName));
+
+ _logger.LogTrace("Request the workflow trigger URL of logic app '{LogicAppName}' in resource group '{ResourceGroup}'", _logicAppName, _resourceGroup);
+ WorkflowTriggerCallbackUrl callbackUrl =
+ await _logicManagementClient.WorkflowTriggers.ListCallbackUrlAsync(_resourceGroup, _logicAppName, triggerName);
+
+ return new LogicAppTriggerUrl
+ {
+ Value = callbackUrl.Value,
+ Method = callbackUrl.Method
+ };
+ }
+
+ private async Task GetTriggerNameAsync()
+ {
+ IPage triggers = await _logicManagementClient.WorkflowTriggers.ListAsync(_resourceGroup, _logicAppName);
+
+ if (triggers.Any())
+ {
+ return triggers.First().Name;
+ }
+
+ throw new LogicAppTriggerNotFoundException(_logicManagementClient.SubscriptionId, _resourceGroup, _logicAppName, $"Cannot find any trigger for logic app '{_logicAppName}' in resource group '{_resourceGroup}'");
+ }
+
+ ///
+ /// Temporary enables a static result for an action with the given on the logic app,
+ /// and disables the static result when the returned instance gets disposed.
+ ///
+ /// The name of the action to enable the static result.
+ ///
+ /// An instance to control when the static result for the action on the logic app should be disabled.
+ ///
+ public async Task TemporaryEnableSuccessStaticResultAsync(string actionName)
+ {
+ Guard.NotNullOrEmpty(actionName, nameof(actionName));
+
+ var successfulStaticResult = new StaticResultDefinition
+ {
+ Outputs = new Outputs { Headers = new Dictionary(), StatusCode = "OK" },
+ Status = "Succeeded"
+ };
+
+ return await TemporaryEnableStaticResultAsync(actionName, successfulStaticResult);
+ }
+
+ ///
+ /// Temporary enables a static result for an action with the given on the logic app,
+ /// and disables the static result when the returned instance gets disposed.
+ ///
+ /// The name of the action to enable the static result.
+ /// The definition that describes the static result for the action.
+ ///
+ /// An instance to control when the static result for the action on the logic app should be disabled.
+ ///
+ public async Task TemporaryEnableStaticResultAsync(string actionName, StaticResultDefinition definition)
+ {
+ Guard.NotNullOrEmpty(actionName, nameof(actionName));
+ Guard.NotNull(definition, nameof(definition));
+
+ return await AsyncDisposable.CreateAsync(
+ async () => await EnableStaticResultForActionsAsync(new Dictionary { [actionName] = definition }),
+ async () => await DisableStaticResultForActionAsync(actionName));
+ }
+
+ ///
+ /// Enables static results for a given set of actions on the logic app.
+ ///
+ /// The set of action names and the corresponding static result.
+ ///
+ /// An instance to control when the static result for the actions on the logic app should be disabled.
+ ///
+ public async Task TemporaryEnableStaticResultsAsync(IDictionary actions)
+ {
+ Guard.NotNull(actions, nameof(actions));
+ Guard.NotAny(actions, nameof(actions));
+
+ return await AsyncDisposable.CreateAsync(
+ async () => await EnableStaticResultForActionsAsync(actions),
+ async () => await DisableStaticResultsForActionsAsync(actions.Keys));
+ }
+
+ private async Task EnableStaticResultForActionsAsync(IDictionary actions)
+ {
+ Guard.NotNull(actions, nameof(actions));
+ Guard.NotAny(actions, nameof(actions));
+ Guard.For(
+ () => actions.Any(action => action.Key is null || action.Value is null),
+ "Cannot enable static result for actions when either the action or result is missing");
+
+ string actionNames = String.Join(", ", actions.Keys);
+ _logger.LogTrace("Enables (+) a static result definition for actions {ActionNames} of logic app '{LogicAppName}' in resource group '{ResourceGroup}'", actionNames, _logicAppName, _resourceGroup);
+
+ Workflow workflow = await _logicManagementClient.Workflows.GetAsync(_resourceGroup, _logicAppName);
+ var logicAppDefinition = JsonConvert.DeserializeObject(workflow.Definition.ToString());
+
+ foreach (KeyValuePair action in actions)
+ {
+ if (logicAppDefinition.Actions[action.Key] is null)
+ {
+ continue;
+ }
+
+ logicAppDefinition = UpdateLogicAppDefinitionWithStaticResult(logicAppDefinition, action.Key, action.Value);
+ }
+
+ workflow.Definition = JObject.Parse(JsonConvert.SerializeObject(logicAppDefinition));
+
+ Workflow resultWorkflow = await _logicManagementClient.Workflows.CreateOrUpdateAsync(_resourceGroup, _logicAppName, workflow);
+ if (resultWorkflow.Name != _logicAppName)
+ {
+ throw new LogicAppNotUpdatedException(_logicManagementClient.SubscriptionId, _resourceGroup, _logicAppName, "Failed to enable a static result.");
+ }
+ }
+
+ private static LogicAppDefinition UpdateLogicAppDefinitionWithStaticResult(
+ LogicAppDefinition logicAppDefinition,
+ string actionName,
+ StaticResultDefinition staticResultDefinition)
+ {
+ ActionDefinition actionDefinition = logicAppDefinition.Actions[actionName];
+ if (actionDefinition.RuntimeConfiguration != null)
+ {
+ actionDefinition.RuntimeConfiguration.StaticResult.StaticResultOptions = "Enabled";
+ }
+ else
+ {
+ string staticResultName = $"{actionName}0";
+ actionDefinition.RuntimeConfiguration = new RuntimeConfiguration
+ {
+ StaticResult = new StaticResult { Name = staticResultName, StaticResultOptions = "Enabled" }
+ };
+
+ if (logicAppDefinition.StaticResults == null)
+ {
+ logicAppDefinition.StaticResults = new Dictionary();
+ }
+
+ logicAppDefinition.StaticResults[staticResultName] = staticResultDefinition;
+ }
+
+ return logicAppDefinition;
+ }
+
+ private async Task DisableStaticResultForActionAsync(string actionName)
+ {
+ Guard.NotNullOrEmpty(actionName, nameof(actionName));
+
+ _logger.LogTrace(
+ "Disables (-) a static result definition for action {ActionName} of logic app '{LogicAppName}' in resource group '{ResourceGroup}'",
+ actionName, _logicAppName, _resourceGroup);
+
+ await DisableStaticResultsForActionAsync(name => name == actionName);
+ }
+
+ private async Task DisableStaticResultsForActionsAsync(IEnumerable actionNames)
+ {
+ Guard.NotNull(actionNames, nameof(actionNames));
+ Guard.NotAny(actionNames, nameof(actionNames));
+ Guard.For(
+ () => actionNames.Any(action => action is null),
+ "Cannot disable static results for actions when one or more action names are missing");
+
+ _logger.LogTrace(
+ "Disables (-) a static result definition for actions {ActionNames} of logic app '{LogicAppName}' in resource group '{ResourceGroup}'",
+ actionNames, _logicAppName, _resourceGroup);
+
+ await DisableStaticResultsForActionAsync(actionNames.Contains);
+ }
+
+ private async Task DisableStaticResultsForActionAsync(Func shouldDisable)
+ {
+ Workflow workflow = await _logicManagementClient.Workflows.GetAsync(_resourceGroup, _logicAppName);
+ var logicAppDefinition = JsonConvert.DeserializeObject(workflow.Definition.ToString());
+
+ foreach (KeyValuePair item in logicAppDefinition.Actions)
+ {
+ ActionDefinition actionDefinition = item.Value;
+ if (shouldDisable(item.Key) && actionDefinition.RuntimeConfiguration != null)
+ {
+ actionDefinition.RuntimeConfiguration.StaticResult.StaticResultOptions = "Disabled";
+ }
+ }
+
+ workflow.Definition = JObject.Parse(JsonConvert.SerializeObject(logicAppDefinition));
+
+ Workflow resultWorkflow = await _logicManagementClient.Workflows.CreateOrUpdateAsync(_resourceGroup, _logicAppName, workflow);
+ if (resultWorkflow.Name != _logicAppName)
+ {
+ throw new LogicAppNotUpdatedException(_logicManagementClient.SubscriptionId, _resourceGroup, _logicAppName, "Failed to disable the static results.");
+ }
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ _logicManagementClient?.Dispose();
+ }
+ }
+}
diff --git a/src/Invictus.Testing/LogicAppConverter.cs b/src/Invictus.Testing/LogicAppConverter.cs
new file mode 100644
index 0000000..afd78e2
--- /dev/null
+++ b/src/Invictus.Testing/LogicAppConverter.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using GuardNet;
+using Invictus.Testing.Model;
+using Microsoft.Azure.Management.Logic.Models;
+using Newtonsoft.Json;
+
+namespace Invictus.Testing
+{
+ ///
+ /// Collection of conversion function to create custom models from Azure SDK models.
+ ///
+ public static class LogicAppConverter
+ {
+ ///
+ /// Convert to .
+ ///
+ public static LogicAppRun ToLogicAppRun(WorkflowRun workFlowRun, IEnumerable actions)
+ {
+ Guard.NotNull(workFlowRun, nameof(workFlowRun));
+ Guard.NotNull(actions, nameof(actions));
+
+ return new LogicAppRun
+ {
+ Id = workFlowRun.Name,
+ StartTime = workFlowRun.StartTime,
+ EndTime = workFlowRun.EndTime,
+ Status = workFlowRun.Status,
+ Error = workFlowRun.Error,
+ CorrelationId = workFlowRun.Correlation?.ClientTrackingId,
+ Trigger = CreateLogicAppTriggerFrom(workFlowRun.Trigger),
+ Actions = actions,
+ TrackedProperties = new ReadOnlyDictionary(GetAllTrackedProperties(actions))
+ };
+ }
+
+ private static LogicAppTrigger CreateLogicAppTriggerFrom(WorkflowRunTrigger workflowRunTrigger)
+ {
+ return new LogicAppTrigger
+ {
+ Name = workflowRunTrigger.Name,
+ Inputs = workflowRunTrigger.Inputs,
+ Outputs = workflowRunTrigger.Outputs,
+ StartTime = workflowRunTrigger.StartTime,
+ EndTime = workflowRunTrigger.EndTime,
+ Status = workflowRunTrigger.Status,
+ Error = workflowRunTrigger.Error
+ };
+ }
+
+ ///
+ /// Convert to .
+ ///
+ public static LogicAppAction ToLogicAppAction(WorkflowRunAction workflowRunAction, dynamic input, dynamic output)
+ {
+ var logicAppAction = new LogicAppAction
+ {
+ Name = workflowRunAction.Name,
+ StartTime = workflowRunAction.StartTime,
+ EndTime = workflowRunAction.EndTime,
+ Status = workflowRunAction.Status,
+ Error = workflowRunAction.Error,
+ Inputs = input,
+ Outputs = output
+ };
+
+ if (workflowRunAction.TrackedProperties != null)
+ {
+ logicAppAction.TrackedProperties =
+ JsonConvert.DeserializeObject>(
+ workflowRunAction.TrackedProperties.ToString());
+ }
+
+ return logicAppAction;
+ }
+
+ private static IDictionary GetAllTrackedProperties(IEnumerable actions)
+ {
+ return actions
+ .Where(x => x.TrackedProperties != null)
+ .OrderByDescending(x => x.StartTime)
+ .SelectMany(a => a.TrackedProperties)
+ .GroupBy(x => x.Key)
+ .Select(g => g.First())
+ .ToDictionary(x => x.Key, x => x.Value);
+ }
+
+ ///
+ /// Convert to .
+ ///
+ public static LogicApp ToLogicApp(Workflow workflow)
+ {
+ Guard.NotNull(workflow, nameof(workflow));
+
+ return new LogicApp
+ {
+ Name = workflow.Name,
+ CreatedTime = workflow.CreatedTime,
+ ChangedTime = workflow.ChangedTime,
+ State = workflow.State,
+ Version = workflow.Version,
+ AccessEndpoint = workflow.AccessEndpoint,
+ Definition = workflow.Definition
+ };
+ }
+ }
+}
diff --git a/src/Invictus.Testing/LogicAppException.cs b/src/Invictus.Testing/LogicAppException.cs
new file mode 100644
index 0000000..5c71918
--- /dev/null
+++ b/src/Invictus.Testing/LogicAppException.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+using GuardNet;
+
+namespace Invictus.Testing
+{
+ ///
+ /// Thrown when a problem occurs during interaction with a logic app running in Azure.
+ ///
+ [Serializable]
+ public class LogicAppException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LogicAppException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the exception.
+ public LogicAppException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the exception.
+ /// The exception that is the cause of the current exception
+ public LogicAppException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID that identifies the subscription on Azure.
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The message that describes the exception.
+ public LogicAppException(
+ string subscriptionId,
+ string resourceGroup,
+ string logicAppName,
+ string message) : base(message)
+ {
+ Guard.NotNullOrWhitespace(subscriptionId, nameof(subscriptionId));
+ Guard.NotNullOrWhitespace(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrWhitespace(logicAppName, nameof(logicAppName));
+
+ SubscriptionId = subscriptionId;
+ ResourceGroup = resourceGroup;
+ LogicAppName = logicAppName;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID that identifies the subscription on Azure.
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The message that describes the exception.
+ /// The exception that is the cause of the current exception
+ public LogicAppException(
+ string subscriptionId,
+ string resourceGroup,
+ string logicAppName,
+ string message,
+ Exception innerException) : base(message, innerException)
+ {
+ Guard.NotNullOrWhitespace(subscriptionId, nameof(subscriptionId));
+ Guard.NotNullOrWhitespace(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrWhitespace(logicAppName, nameof(logicAppName));
+
+ SubscriptionId = subscriptionId;
+ ResourceGroup = resourceGroup;
+ LogicAppName = logicAppName;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
+ protected LogicAppException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ LogicAppName = info.GetString(nameof(LogicAppName));
+ ResourceGroup = info.GetString(nameof(ResourceGroup));
+ SubscriptionId = info.GetString(nameof(SubscriptionId));
+ }
+
+ ///
+ /// Gets the ID of the subscription of
+ ///
+ public string SubscriptionId { get; }
+
+ ///
+ /// Gets the resource group on Azure in which the logic app is located.
+ ///
+ public string ResourceGroup { get; }
+
+ ///
+ /// Gets the name of the logic app running on Azure.
+ ///
+ public string LogicAppName { get; }
+
+ ///
+ /// When overridden in a derived class, sets the with information about the exception.
+ ///
+ /// The that holds the serialized object data about the exception being thrown.
+ /// The that contains contextual information about the source or destination.
+ /// The info parameter is a null reference (Nothing in Visual Basic).
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ Guard.NotNull(info, nameof(info));
+
+ info.AddValue(nameof(SubscriptionId), SubscriptionId);
+ info.AddValue(nameof(ResourceGroup), ResourceGroup);
+ info.AddValue(nameof(LogicAppName), LogicAppName);
+
+ base.GetObjectData(info, context);
+ }
+ }
+}
diff --git a/src/Invictus.Testing/LogicAppNotUpdatedException.cs b/src/Invictus.Testing/LogicAppNotUpdatedException.cs
new file mode 100644
index 0000000..86aa39f
--- /dev/null
+++ b/src/Invictus.Testing/LogicAppNotUpdatedException.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+
+namespace Invictus.Testing
+{
+ ///
+ /// Thrown when the logic app running in Azure could not be updated.
+ ///
+ [Serializable]
+ public class LogicAppNotUpdatedException : LogicAppException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LogicAppNotUpdatedException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the exception.
+ public LogicAppNotUpdatedException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the exception.
+ /// The exception that is the cause of the current exception
+ public LogicAppNotUpdatedException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID that identifies the subscription on Azure.
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The message that describes the exception.
+ public LogicAppNotUpdatedException(
+ string subscriptionId,
+ string resourceGroup,
+ string logicAppName,
+ string message) : base(subscriptionId, resourceGroup, logicAppName, message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID that identifies the subscription on Azure.
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The message that describes the exception.
+ /// The exception that is the cause of the current exception
+ public LogicAppNotUpdatedException(
+ string subscriptionId,
+ string resourceGroup,
+ string logicAppName,
+ string message,
+ Exception innerException) : base(subscriptionId, resourceGroup, logicAppName, message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
+ protected LogicAppNotUpdatedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/Invictus.Testing/LogicAppTriggerNotFoundException.cs b/src/Invictus.Testing/LogicAppTriggerNotFoundException.cs
new file mode 100644
index 0000000..7cd4b01
--- /dev/null
+++ b/src/Invictus.Testing/LogicAppTriggerNotFoundException.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+
+namespace Invictus.Testing
+{
+ ///
+ /// Exception thrown when no trigger can be found for a given logic app.
+ ///
+ [Serializable]
+ public class LogicAppTriggerNotFoundException : LogicAppException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LogicAppTriggerNotFoundException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the exception.
+ public LogicAppTriggerNotFoundException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the exception.
+ /// The exception that is the cause of the current exception
+ public LogicAppTriggerNotFoundException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID that identifies the subscription on Azure.
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The message that describes the exception.
+ public LogicAppTriggerNotFoundException(
+ string subscriptionId,
+ string resourceGroup,
+ string logicAppName,
+ string message) : base(subscriptionId, resourceGroup, logicAppName, message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ID that identifies the subscription on Azure.
+ /// The resource group where the logic app is located.
+ /// The name of the logic app resource running in Azure.
+ /// The message that describes the exception.
+ /// The exception that is the cause of the current exception
+ public LogicAppTriggerNotFoundException(
+ string subscriptionId,
+ string resourceGroup,
+ string logicAppName,
+ string message,
+ Exception innerException) : base(subscriptionId, resourceGroup, logicAppName, message, innerException)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
+ protected LogicAppTriggerNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/Invictus.Testing/LogicAppsHelper.cs b/src/Invictus.Testing/LogicAppsHelper.cs
deleted file mode 100644
index b342b92..0000000
--- a/src/Invictus.Testing/LogicAppsHelper.cs
+++ /dev/null
@@ -1,729 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Invictus.Testing.Model;
-using Invictus.Testing.Serialization;
-using Microsoft.Azure.Management.Logic;
-using Microsoft.Azure.Management.Logic.Models;
-using Microsoft.IdentityModel.Clients.ActiveDirectory;
-using Microsoft.Rest;
-using Microsoft.Rest.Azure.OData;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Polly;
-using Polly.Retry;
-
-namespace Invictus.Testing
-{
- public class LogicAppsHelper : IDisposable
- {
- private readonly LogicManagementClient _logicManagementClient;
- private readonly string _resourceGroupPrefix;
- private readonly string _logicAppPrefix;
-
- private const int DefaultTimeoutInSeconds = 90;
- private const int PollIntervalInSeconds = 5;
-
- ///
- /// LogicAppsHelper constructor.
- ///
- /// The Id of the Azure Subscription hosting the Logic Apps.
- /// The Directory Id of the Azure Subscription AD
- /// The Object Id of the Service Principal with sufficient access to all required resource groups.
- /// The corresponding key of the Service Principal
- public LogicAppsHelper(string subscriptionId, string tenantId, string clientId, string clientSecret)
- {
- _logicManagementClient = GetLogicManagementClientAsync(subscriptionId, tenantId, clientId, clientSecret).Result;
- }
-
- ///
- /// LogicAppsHelper constructor.
- ///
- /// The Id of the Azure Subscription hosting the Logic Apps.
- /// The Directory Id of the Azure Subscription AD
- /// The Object Id of the Service Principal with sufficient access to all required resource groups.
- /// The corresponding key of the Service Principal
- /// Prefix for Resource Group
- /// Prefix for Logic App name
- public LogicAppsHelper(string subscriptionId, string tenantId, string clientId, string clientSecret, string resourceGroupPrefix = "", string logicAppPrefix = "")
- {
- _resourceGroupPrefix = resourceGroupPrefix;
- _logicAppPrefix = logicAppPrefix;
-
- _logicManagementClient = GetLogicManagementClientAsync(subscriptionId, tenantId, clientId, clientSecret).Result;
- }
-
- ///
- /// Enable a Logic App.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- ///
- public async Task EnableLogicAppAsync(string resourceGroupName, string logicAppName)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- await _logicManagementClient.Workflows.EnableAsync(resourceGroupName, logicAppName);
- }
-
- ///
- /// Disable a Logic App.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- ///
- public async Task DisableLogicAppAsync(string resourceGroupName, string logicAppName)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- await _logicManagementClient.Workflows.DisableAsync(resourceGroupName, logicAppName);
- }
-
- ///
- /// Delete a Logic App.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- ///
- public async Task DeleteLogicAppAsync(string resourceGroupName, string logicAppName)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- await _logicManagementClient.Workflows.DeleteAsync(resourceGroupName, logicAppName);
- }
-
- ///
- /// Get Logic App.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// A LogicApp object
- public async Task GetLogicAppAsync(string resourceGroupName, string logicAppName)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- var workflow = await _logicManagementClient.Workflows.GetAsync(resourceGroupName, logicAppName);
- return (LogicApp)workflow;
- }
-
- ///
- /// Update Logic App definition.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp definition in JSON format
- /// True is operation succeeded, false otherwise
- public async Task UpdateLogicAppAsync(string resourceGroupName, string logicAppName, string logicAppDefinition)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- var success = false;
- var workflow = await _logicManagementClient.Workflows.GetAsync(resourceGroupName, logicAppName);
-
- workflow.Definition = JObject.Parse(logicAppDefinition);
- var resultWorkflow = await
- _logicManagementClient.Workflows.CreateOrUpdateAsync(resourceGroupName, logicAppName, workflow);
-
- if (resultWorkflow.Name == logicAppName)
- {
- success = true;
- }
-
- return success;
- }
-
- ///
- /// Enable default static result for Logic App action.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp action name
- /// True is operation succeeded, false otherwise
- public async Task EnableStaticResultForActionAsync(string resourceGroupName, string logicAppName, string actionName)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- var success = false;
-
- var workflow = await _logicManagementClient.Workflows.GetAsync(resourceGroupName, logicAppName);
- LogicAppDefinition logicAppDefinition = JsonConvert.DeserializeObject(workflow.Definition.ToString());
-
- ActionDefinition actionDefinition = logicAppDefinition.Actions[actionName];
- if (actionDefinition == null) return false;
-
- if (actionDefinition.RuntimeConfiguration != null)
- {
- actionDefinition.RuntimeConfiguration.StaticResult.StaticResultOptions = "Enabled";
- }
- else
- {
- var staticResultName = $"{actionName}0";
- actionDefinition.RuntimeConfiguration = new RuntimeConfiguration
- {
- StaticResult = new StaticResult { Name = staticResultName, StaticResultOptions = "Enabled" }
- };
-
- StaticResultDefinition staticResultDefinition = new StaticResultDefinition
- {
- Outputs = new Outputs { Headers = new Dictionary(), StatusCode = "OK" },
- Status = "Succeeded"
- };
- if (logicAppDefinition.StaticResults == null)
- logicAppDefinition.StaticResults = new Dictionary();
-
- logicAppDefinition.StaticResults.Add(staticResultName, staticResultDefinition);
- }
-
- workflow.Definition = JObject.Parse(JsonConvert.SerializeObject(logicAppDefinition));
-
- var resultWorkflow = await _logicManagementClient.Workflows.CreateOrUpdateAsync(resourceGroupName, logicAppName, workflow);
- if (resultWorkflow.Name == logicAppName)
- {
- success = true;
- }
-
- return success;
- }
-
- ///
- /// Enable static result for Logic App action.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp action name
- /// The Static Result definition
- /// True is operation succeeded, false otherwise
- public async Task EnableStaticResultForActionAsync(string resourceGroupName, string logicAppName, string actionName, StaticResultDefinition staticResultDefinition)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- var success = false;
-
- var workflow = await _logicManagementClient.Workflows.GetAsync(resourceGroupName, logicAppName);
- LogicAppDefinition logicAppDefinition = JsonConvert.DeserializeObject(workflow.Definition.ToString());
-
- ActionDefinition actionDefinition = logicAppDefinition.Actions[actionName];
- if (actionDefinition == null) return false;
-
- var staticResultName = $"{actionName}0";
- if (actionDefinition.RuntimeConfiguration != null)
- {
- actionDefinition.RuntimeConfiguration.StaticResult.StaticResultOptions = "Enabled";
- }
- else
- {
- actionDefinition.RuntimeConfiguration = new RuntimeConfiguration
- {
- StaticResult = new StaticResult { Name = staticResultName, StaticResultOptions = "Enabled" }
- };
- }
-
- if (logicAppDefinition.StaticResults == null)
- logicAppDefinition.StaticResults = new Dictionary();
-
- if (logicAppDefinition.StaticResults.ContainsKey(staticResultName))
- logicAppDefinition.StaticResults[staticResultName] = staticResultDefinition;
- else
- logicAppDefinition.StaticResults.Add(staticResultName, staticResultDefinition);
-
- workflow.Definition = JObject.Parse(JsonConvert.SerializeObject(logicAppDefinition));
-
- var resultWorkflow = await _logicManagementClient.Workflows.CreateOrUpdateAsync(resourceGroupName, logicAppName, workflow);
- if (resultWorkflow.Name == logicAppName)
- {
- success = true;
- }
-
- return success;
- }
-
- ///
- /// Enable static result for Logic App actions.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// List of LogicApp actions and static result definitions
- /// True is operation succeeded, false otherwise
- public async Task EnableStaticResultForActionsAsync(string resourceGroupName, string logicAppName, Dictionary actions)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- var success = false;
-
- var workflow = await _logicManagementClient.Workflows.GetAsync(resourceGroupName, logicAppName);
- LogicAppDefinition logicAppDefinition = JsonConvert.DeserializeObject(workflow.Definition.ToString());
-
- foreach (var item in actions)
- {
- var actionName = item.Key;
- var staticResultDefinition = item.Value;
- ActionDefinition actionDefinition = logicAppDefinition.Actions[actionName];
- if (actionDefinition == null) continue;
-
- var staticResultName = $"{actionName}0";
- if (actionDefinition.RuntimeConfiguration != null)
- {
- actionDefinition.RuntimeConfiguration.StaticResult.StaticResultOptions = "Enabled";
- }
- else
- {
- actionDefinition.RuntimeConfiguration = new RuntimeConfiguration
- {
- StaticResult = new StaticResult { Name = staticResultName, StaticResultOptions = "Enabled" }
- };
- }
-
- if (logicAppDefinition.StaticResults == null)
- logicAppDefinition.StaticResults = new Dictionary();
-
- if (logicAppDefinition.StaticResults.ContainsKey(staticResultName))
- logicAppDefinition.StaticResults[staticResultName] = staticResultDefinition;
- else
- logicAppDefinition.StaticResults.Add(staticResultName, staticResultDefinition);
- }
-
-
- workflow.Definition = JObject.Parse(JsonConvert.SerializeObject(logicAppDefinition));
- var resultWorkflow = await _logicManagementClient.Workflows.CreateOrUpdateAsync(resourceGroupName, logicAppName, workflow);
- if (resultWorkflow.Name == logicAppName)
- {
- success = true;
- }
-
- return success;
- }
-
- ///
- /// Disable static result for Logic App action.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp action name
- /// True is operation succeeded, false otherwise
- public async Task DisableStaticResultForActionAsync(string resourceGroupName, string logicAppName, string actionName)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- var success = false;
-
- var workflow = await _logicManagementClient.Workflows.GetAsync(resourceGroupName, logicAppName);
- LogicAppDefinition logicAppDefinition = JsonConvert.DeserializeObject(workflow.Definition.ToString());
-
- ActionDefinition actionDefinition = logicAppDefinition.Actions[actionName];
- if (actionDefinition == null) return false;
-
- if (actionDefinition.RuntimeConfiguration != null)
- {
- actionDefinition.RuntimeConfiguration.StaticResult.StaticResultOptions = "Disabled";
- }
-
- workflow.Definition = JObject.Parse(JsonConvert.SerializeObject(logicAppDefinition));
-
- var resultWorkflow = await _logicManagementClient.Workflows.CreateOrUpdateAsync(resourceGroupName, logicAppName, workflow);
- if (resultWorkflow.Name == logicAppName)
- {
- success = true;
- }
-
- return success;
- }
-
- ///
- /// Disables static results for all actions of a Logic App.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- ///
- public async Task DisableAllStaticResultsForLogicAppAsync(string resourceGroupName, string logicAppName)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- var success = false;
-
- var workflow = await _logicManagementClient.Workflows.GetAsync(resourceGroupName, logicAppName);
- LogicAppDefinition logicAppDefinition = JsonConvert.DeserializeObject(workflow.Definition.ToString());
-
- foreach (var item in logicAppDefinition.Actions)
- {
- ActionDefinition actionDefinition = item.Value;
-
- if (actionDefinition.RuntimeConfiguration != null)
- {
- actionDefinition.RuntimeConfiguration.StaticResult.StaticResultOptions = "Disabled";
- }
-
- }
-
- workflow.Definition = JObject.Parse(JsonConvert.SerializeObject(logicAppDefinition));
-
- var resultWorkflow = await _logicManagementClient.Workflows.CreateOrUpdateAsync(resourceGroupName, logicAppName, workflow);
- if (resultWorkflow.Name == logicAppName)
- {
- success = true;
- }
-
- return success;
- }
-
- ///
- /// Get Logic App Trigger Url
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp trigger name
- /// A LogicAppTriggerUrl object
- public async Task GetLogicAppTriggerUrlAsync(string resourceGroupName, string logicAppName, string triggerName = "")
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- if (string.IsNullOrEmpty(triggerName))
- {
- triggerName = await GetTriggerName(resourceGroupName, logicAppName);
- }
- var callbackUrl = await _logicManagementClient.WorkflowTriggers.ListCallbackUrlAsync(resourceGroupName, logicAppName, triggerName);
-
- return new LogicAppTriggerUrl { Value = callbackUrl.Value, Method = callbackUrl.Method };
- }
-
- ///
- /// Runs a Logic App.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp trigger name
- ///
- public async Task RunLogicAppAsync(string resourceGroupName, string logicAppName, string triggerName = "")
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- if (string.IsNullOrEmpty(triggerName))
- {
- triggerName = await GetTriggerName(resourceGroupName, logicAppName);
- }
-
- await _logicManagementClient.WorkflowTriggers.RunAsync(resourceGroupName, logicAppName, triggerName);
- }
-
- ///
- /// Get Logic App Run By Id.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp run identifier
- /// A LogicApp run
- public async Task GetLogicAppRunAsync(string resourceGroupName, string logicAppName, string Id)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- var workflowRun = await _logicManagementClient.WorkflowRuns.GetAsync(resourceGroupName, logicAppName, Id);
- return Converter.ToLogicAppRun(this, resourceGroupName, logicAppName, workflowRun);
- }
-
- ///
- /// Poll for logic app run by correlation id.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The start time of the LogicApp run
- /// The correlation Id
- /// The timeout period in seconds
- /// A LogicApp run
- public async Task PollForLogicAppRunAsync(string resourceGroupName, string logicAppName, DateTime startTime, string correlationId,
- TimeSpan? timeout = null)
- {
- if (timeout == null) { timeout = TimeSpan.FromSeconds(DefaultTimeoutInSeconds); }
-
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- var odataQuery = GetOdataQuery(startTime, correlationId);
-
- return await Poll(
- async () =>
- {
- var result = await _logicManagementClient.WorkflowRuns.ListAsync(resourceGroupName, logicAppName, odataQuery);
- return result.Select(x => Converter.ToLogicAppRun(this, resourceGroupName, logicAppName, x)).FirstOrDefault();
- },
- PollIntervalInSeconds,
- timeout.Value);
- }
-
- ///
- /// Poll for logic app runs after timeout expired.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The start time of the LogicApp run
- /// The correlation Id
- /// The timeout period in seconds
- /// Expected number of items
- /// List of LogicApp runs
- public async Task> PollForLogicAppRunsAsync(string resourceGroupName, string logicAppName, DateTime startTime, string correlationId,
- TimeSpan? timeout = null, int numberOfItems = 0)
- {
- if (timeout == null) { timeout = TimeSpan.FromSeconds(DefaultTimeoutInSeconds); }
-
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- if (numberOfItems > 0)
- {
- return await Poll(
- async () => await FindLogicAppRunsByCorrelationIdAsync(resourceGroupName, logicAppName, startTime, correlationId),
- numberOfItems,
- PollIntervalInSeconds,
- timeout.Value);
- }
- else
- {
- return await PollAfterTimeout(
- async () => await FindLogicAppRunsByCorrelationIdAsync(resourceGroupName, logicAppName, startTime, correlationId),
- timeout.Value);
- }
- }
-
- ///
- /// Poll for logic app run by tracked property.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The start time of the LogicApp run
- /// Tracked Property name
- /// Tracked Property value
- /// The timeout period in seconds
- /// A LogicApp run
- public async Task PollForLogicAppRunAsync(string resourceGroupName, string logicAppName, DateTime startTime,
- string trackedPropertyName, string trackedProperyValue, TimeSpan? timeout = null)
- {
- if (timeout == null) { timeout = TimeSpan.FromSeconds(DefaultTimeoutInSeconds); }
-
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- return await Poll(
- async () => await FindLogicAppRunByTrackedPropertyAsync(resourceGroupName, logicAppName, startTime, trackedPropertyName, trackedProperyValue),
- PollIntervalInSeconds,
- timeout.Value
- );
- }
-
- ///
- /// Poll for logic app runs after timeout expired.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The start time of the LogicApp run
- /// Tracked Property name
- /// Tracked Property value
- /// The timeout period in seconds
- /// Expected number of items
- /// List of LogicApp runs
- public async Task> PollForLogicAppRunsAsync(string resourceGroupName, string logicAppName, DateTime startTime,
- string trackedPropertyName, string trackedProperyValue, TimeSpan? timeout = null, int numberOfItems = 0)
- {
- if (timeout == null) { timeout = TimeSpan.FromSeconds(DefaultTimeoutInSeconds); }
-
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
-
- if (numberOfItems > 0)
- {
- return await Poll(
- async () => await FindLogicAppRunsByTrackedPropertyAsync(resourceGroupName, logicAppName, startTime, trackedPropertyName, trackedProperyValue),
- numberOfItems,
- PollIntervalInSeconds,
- timeout.Value);
- }
- else
- {
- return await PollAfterTimeout(
- async () => await FindLogicAppRunsByTrackedPropertyAsync(resourceGroupName, logicAppName, startTime, trackedPropertyName, trackedProperyValue),
- timeout.Value);
- }
- }
-
- ///
- /// Get LogicApp Run actions.
- ///
- /// The Azure Resource Group
- /// The LogicApp name
- /// The LogicApp Run Identifier
- ///
- /// List of LogicApp actions
- public async Task> GetLogicAppRunActionsAsync(string resourceGroupName, string logicAppName, string runName, bool usePrefix = true)
- {
- if (usePrefix)
- {
- resourceGroupName = PrefixResourceGroupName(resourceGroupName);
- logicAppName = PrefixLogicAppName(logicAppName);
- }
-
- return await FindLogicAppRunActionsAsync(resourceGroupName, logicAppName, runName);
- }
-
- #region Private Methods
- private async Task GetLogicManagementClientAsync(string subscriptionId, string tenantId, string clientId, string clientSecret)
- {
- var authority = string.Format("{0}{1}", "https://login.windows.net/", tenantId);
-
- var authContext = new AuthenticationContext(authority);
- var credential = new ClientCredential(clientId, clientSecret);
-
- var token = await authContext.AcquireTokenAsync("https://management.azure.com/", credential);
-
- return new LogicManagementClient(new TokenCredentials(token.AccessToken)) { SubscriptionId = subscriptionId };
- }
-
- private async Task FindLogicAppRunByTrackedPropertyAsync(string resourceGroupName, string logicAppName, DateTime startTime, string trackedPropertyName, string trackedProperyValue)
- {
- LogicAppRun result = null;
-
- var odataQuery = new ODataQuery
- {
- Filter = $"StartTime ge {startTime.ToString("O")} and Status ne 'Running'"
- };
- var workFlowRuns = await _logicManagementClient.WorkflowRuns.ListAsync(resourceGroupName, logicAppName, odataQuery);
-
- foreach (var workFlowRun in workFlowRuns)
- {
- var actions = await FindLogicAppRunActionsAsync(resourceGroupName, logicAppName, workFlowRun.Name);
-
- bool trackedPropertyFound = actions
- .Where(a => a.TrackedProperties != null)
- .Any(a => a.TrackedProperties.Count(t => t.Key.Equals(trackedPropertyName, StringComparison.OrdinalIgnoreCase) && t.Value.Equals(trackedProperyValue, StringComparison.OrdinalIgnoreCase)) > 0);
-
- if (trackedPropertyFound)
- {
- result = Converter.ToLogicAppRun(workFlowRun, actions);
- break;
- }
- }
-
- return result;
- }
-
- private async Task> FindLogicAppRunsByTrackedPropertyAsync(string resourceGroupName, string logicAppName, DateTime startTime, string trackedPropertyName, string trackedProperyValue)
- {
- var result = new List();
-
- var odataQuery = new ODataQuery
- {
- Filter = $"StartTime ge {startTime.ToString("O")} and Status ne 'Running'"
- };
- var workFlowRuns = await _logicManagementClient.WorkflowRuns.ListAsync(resourceGroupName, logicAppName, odataQuery);
-
- Parallel.ForEach(workFlowRuns, (workFlowRun) =>
- {
- var actions = FindLogicAppRunActionsAsync(resourceGroupName, logicAppName, workFlowRun.Name).Result;
-
- bool trackedPropertyFound = actions
- .Where(a => a.TrackedProperties != null)
- .Any(a => a.TrackedProperties.Count(t => t.Key.Equals(trackedPropertyName, StringComparison.OrdinalIgnoreCase) && t.Value.Equals(trackedProperyValue, StringComparison.OrdinalIgnoreCase)) > 0);
-
- if (trackedPropertyFound)
- {
- var logicAppRun = Converter.ToLogicAppRun(workFlowRun, actions);
- result.Add(logicAppRun);
- }
-
- });
-
- return result;
- }
-
- private async Task> FindLogicAppRunsByCorrelationIdAsync(string resourceGroupName, string logicAppName, DateTime startTime, string correlationId)
- {
- var odataQuery = GetOdataQuery(startTime, correlationId);
-
- var result = await _logicManagementClient.WorkflowRuns.ListAsync(resourceGroupName, logicAppName, odataQuery);
- return result.Select(x => Converter.ToLogicAppRun(this, resourceGroupName, logicAppName, x)).ToList();
- }
-
- private async Task> FindLogicAppRunActionsAsync(string resourceGroupName, string logicAppName, string runName)
- {
- var actions = new List();
-
- var workflowRunActions = await _logicManagementClient.WorkflowRunActions.ListAsync(resourceGroupName, logicAppName, runName);
- foreach (var workflowRunAction in workflowRunActions)
- {
- actions.Add(await Converter.ToLogicAppActionAsync(workflowRunAction));
- }
-
- return actions;
- }
-
- private async Task GetTriggerName(string resourceGroupName, string logicAppName)
- {
- var triggerName = string.Empty;
- var triggers = await _logicManagementClient.WorkflowTriggers.ListAsync(resourceGroupName, logicAppName);
- if (triggers.Count() > 0)
- {
- triggerName = triggers.First().Name;
- }
-
- return triggerName;
- }
-
- private string PrefixResourceGroupName(string resourceGroupName)
- {
- return $"{_resourceGroupPrefix}{resourceGroupName}";
- }
-
- private string PrefixLogicAppName(string logicAppName)
- {
- return $"{_logicAppPrefix}{logicAppName}";
- }
-
- private ODataQuery GetOdataQuery(DateTime startTime, string correlationId)
- {
- return new ODataQuery
- {
- Filter = $"StartTime ge {startTime.ToString("O")} and ClientTrackingId eq '{correlationId}' and Status ne 'Running'"
- };
- }
-
- private async Task Poll(Func> condition, int pollIntervalSeconds, TimeSpan timeout)
- {
- RetryPolicy retryPolicy =
- Policy.HandleResult(result => result == null)
- .WaitAndRetryForeverAsync(index => TimeSpan.FromSeconds(pollIntervalSeconds));
-
- return await Policy.TimeoutAsync(timeout)
- .WrapAsync(retryPolicy)
- .ExecuteAsync(condition);
- }
-
- private async Task> Poll(Func>> condition, int count, int pollIntervalSeconds, TimeSpan timeout)
- {
- RetryPolicy> retryPolicy =
- Policy.HandleResult>(results => results.Count < count)
- .WaitAndRetryForeverAsync(index => TimeSpan.FromSeconds(pollIntervalSeconds));
-
- return await Policy.TimeoutAsync(timeout)
- .WrapAsync(retryPolicy)
- .ExecuteAsync(condition);
- }
-
- private async Task PollAfterTimeout(Func> returnDelegate, TimeSpan timeout)
- {
- await Task.Delay(timeout);
- return await returnDelegate();
- }
- #endregion
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- public void Dispose()
- {
- _logicManagementClient?.Dispose();
- }
- }
-}
diff --git a/src/Invictus.Testing/LogicAppsProvider.cs b/src/Invictus.Testing/LogicAppsProvider.cs
new file mode 100644
index 0000000..e8e328a
--- /dev/null
+++ b/src/Invictus.Testing/LogicAppsProvider.cs
@@ -0,0 +1,341 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using GuardNet;
+using Invictus.Testing.Model;
+using Microsoft.Azure.Management.Logic;
+using Microsoft.Azure.Management.Logic.Models;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Rest.Azure;
+using Microsoft.Rest.Azure.OData;
+using Newtonsoft.Json.Linq;
+using Polly;
+using Polly.Retry;
+using Polly.Timeout;
+
+namespace Invictus.Testing
+{
+ ///
+ /// Component to provide access in a reliable manner on logic app resources running in Azure.
+ ///
+ public class LogicAppsProvider
+ {
+ private readonly string _resourceGroup, _logicAppName;
+ private readonly LogicAuthentication _authentication;
+ private readonly TimeSpan _retryInterval = TimeSpan.FromSeconds(1);
+ private readonly ILogger _logger;
+
+ private DateTimeOffset _startTime = DateTimeOffset.UtcNow;
+ private TimeSpan _timeout = TimeSpan.FromSeconds(90);
+ private string _trackedPropertyName, _trackedPropertyValue, _correlationId;
+ private bool _hasTrackedProperty, _hasCorrelationId;
+
+ private static readonly HttpClient HttpClient = new HttpClient();
+
+ private LogicAppsProvider(
+ string resourceGroup,
+ string logicAppName,
+ LogicAuthentication authentication,
+ ILogger logger)
+ {
+ Guard.NotNullOrWhitespace(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrWhitespace(logicAppName, nameof(logicAppName));
+ Guard.NotNull(authentication, nameof(authentication));
+ Guard.NotNull(logger, nameof(logger));
+
+ _resourceGroup = resourceGroup;
+ _logicAppName = logicAppName;
+ _authentication = authentication;
+ _logger = logger;
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The resource group where the logic apps should be located.
+ /// The name of the logic app resource running in Azure.
+ /// The authentication mechanism to authenticate with Azure.
+ public static LogicAppsProvider LocatedAt(
+ string resourceGroup,
+ string logicAppName,
+ LogicAuthentication authentication)
+ {
+ Guard.NotNullOrWhitespace(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrWhitespace(logicAppName, nameof(logicAppName));
+ Guard.NotNull(authentication, nameof(authentication));
+
+ return LocatedAt(resourceGroup, logicAppName, authentication, NullLogger.Instance);
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The resource group where the logic apps should be located.
+ /// The name of the logic app resource running in Azure.
+ /// The authentication mechanism to authenticate with Azure.
+ /// The instance to write diagnostic trace messages while interacting with the provider.
+ public static LogicAppsProvider LocatedAt(
+ string resourceGroup,
+ string logicAppName,
+ LogicAuthentication authentication,
+ ILogger logger)
+ {
+ Guard.NotNullOrEmpty(resourceGroup, nameof(resourceGroup));
+ Guard.NotNullOrEmpty(logicAppName, nameof(logicAppName));
+ Guard.NotNull(authentication, nameof(authentication));
+
+ logger = logger ?? NullLogger.Instance;
+ return new LogicAppsProvider(resourceGroup, logicAppName, authentication, logger);
+ }
+
+ ///
+ /// Sets the start time when the logic app runs were executed.
+ ///
+ /// The date that the logic app ran.
+ public LogicAppsProvider WithStartTime(DateTimeOffset startTime)
+ {
+ _startTime = startTime;
+ return this;
+ }
+
+ ///
+ /// Sets the time period in which the retrieval of the logic app runs should succeed.
+ ///
+ /// The period to retrieve logic app runs.
+ public LogicAppsProvider WithTimeout(TimeSpan timeout)
+ {
+ _timeout = timeout;
+ return this;
+ }
+
+ ///
+ /// Sets the correlation ID as client tracking when retrieving logic app runs.
+ ///
+ /// The client tracking of the logic app runs.
+ public LogicAppsProvider WithCorrelationId(string correlationId)
+ {
+ Guard.NotNull(correlationId, nameof(correlationId));
+
+ _hasCorrelationId = true;
+ _correlationId = correlationId;
+ return this;
+ }
+
+ ///
+ /// Sets the tracking property as filter when retrieving logic app runs.
+ ///
+ /// The name of the tracked property to filter on.
+ /// The value of the tracked property to filter on.
+ public LogicAppsProvider WithTrackedProperty(string trackedPropertyName, string trackedPropertyValue)
+ {
+ Guard.NotNull(trackedPropertyName, nameof(trackedPropertyName));
+ Guard.NotNull(trackedPropertyValue, nameof(trackedPropertyValue));
+
+ _hasTrackedProperty = true;
+ _trackedPropertyName = trackedPropertyName;
+ _trackedPropertyValue = trackedPropertyValue;
+ return this;
+ }
+
+ ///
+ /// Starts polling for a series of logic app runs corresponding to the previously set filtering criteria.
+ ///
+ public async Task> PollForLogicAppRunsAsync()
+ {
+ var cancellationTokenSource = new CancellationTokenSource();
+ cancellationTokenSource.CancelAfter(_timeout);
+
+ IEnumerable logicAppRuns = Enumerable.Empty();
+
+ while (!cancellationTokenSource.Token.IsCancellationRequested)
+ {
+ try
+ {
+ logicAppRuns = await GetLogicAppRunsAsync();
+ await Task.Delay(TimeSpan.FromSeconds(1), cancellationTokenSource.Token);
+ }
+ catch (Exception exception)
+ {
+ _logger.LogError(exception, "Polling for logic app runs was faulted: {Message}", exception.Message);
+ }
+ }
+
+ return logicAppRuns ?? Enumerable.Empty();
+ }
+
+ ///
+ /// Start polling for a single logic app run corresponding to the previously set filtering criteria.
+ ///
+ public async Task PollForSingleLogicAppRunAsync()
+ {
+ IEnumerable logicAppRuns = await PollForLogicAppRunsAsync(minimumNumberOfItems: 1);
+ return logicAppRuns.FirstOrDefault();
+ }
+
+ ///
+ /// Starts polling for a corresponding to the previously set filtering criteria.
+ ///
+ /// The minimum amount of logic app runs to retrieve.
+ public async Task> PollForLogicAppRunsAsync(int minimumNumberOfItems)
+ {
+ Guard.NotLessThanOrEqualTo(minimumNumberOfItems, 0, nameof(minimumNumberOfItems));
+
+ string amount = minimumNumberOfItems == 1 ? "any" : minimumNumberOfItems.ToString();
+
+ RetryPolicy> retryPolicy =
+ Policy.HandleResult>(currentLogicAppRuns =>
+ {
+ int count = currentLogicAppRuns.Count();
+ bool isStillPending = count < minimumNumberOfItems;
+
+ _logger.LogTrace("Polling for {Amount} log app runs, whilst got now {Current} ", amount, count);
+ return isStillPending;
+ }).Or(ex =>
+ {
+ _logger.LogError(ex, "Polling for logic app runs was faulted: {Message}", ex.Message);
+ return true;
+ })
+ .WaitAndRetryForeverAsync(index =>
+ {
+ _logger.LogTrace("Could not retrieve logic app runs in time, wait 1s and try again...");
+ return _retryInterval;
+ });
+
+ PolicyResult> result =
+ await Policy.TimeoutAsync(_timeout)
+ .WrapAsync(retryPolicy)
+ .ExecuteAndCaptureAsync(GetLogicAppRunsAsync);
+
+ if (result.Outcome == OutcomeType.Failure)
+ {
+ if (result.FinalException is null
+ || result.FinalException.GetType() == typeof(TimeoutRejectedException))
+ {
+ string correlation = _hasCorrelationId
+ ? $"{Environment.NewLine} with correlation property equal '{_correlationId}'"
+ : String.Empty;
+
+ string trackedProperty = _hasTrackedProperty
+ ? $" {Environment.NewLine} with tracked property [{_trackedPropertyName}] = {_trackedPropertyValue}"
+ : String.Empty;
+
+ throw new TimeoutException(
+ $"Could not in the given timeout span ({_timeout:g}) retrieve the expected amount of logic app runs "
+ + $"{Environment.NewLine} with StartTime <= {_startTime:O}"
+ + correlation
+ + trackedProperty);
+ }
+
+ throw result.FinalException;
+ }
+
+ _logger.LogTrace("Polling finished successful with {LogicAppRunsCount} logic app runs", result.Result.Count());
+ return result.Result;
+ }
+
+ private async Task>> ExecutePolicy(RetryPolicy> retryPolicy)
+ {
+ PolicyResult> result =
+ await Policy.TimeoutAsync(_timeout)
+ .WrapAsync(retryPolicy)
+ .ExecuteAndCaptureAsync(GetLogicAppRunsAsync);
+ return result;
+ }
+
+ private async Task> GetLogicAppRunsAsync()
+ {
+ using (LogicManagementClient managementClient = await _authentication.AuthenticateAsync())
+ {
+ var odataQuery = new ODataQuery
+ {
+ Filter = $"StartTime ge {_startTime.UtcDateTime:O} and Status ne 'Running'"
+ };
+
+ if (_hasCorrelationId)
+ {
+ odataQuery.Filter += $" and ClientTrackingId eq '{_correlationId}'";
+ }
+
+ _logger.LogTrace(
+ "Query logic app runs for '{LogicAppName}' in resource group '{ResourceGroup}': {Query}", _logicAppName, _resourceGroup, odataQuery.Filter);
+
+ IPage workFlowRuns =
+ await managementClient.WorkflowRuns.ListAsync(_resourceGroup, _logicAppName, odataQuery);
+
+ _logger.LogTrace("Query returned {WorkFlowRunCount} workflow runs", workFlowRuns.Count());
+
+ var logicAppRuns = new Collection();
+ foreach (WorkflowRun workFlowRun in workFlowRuns)
+ {
+ IEnumerable actions =
+ await FindLogicAppRunActionsAsync(managementClient, workFlowRun.Name);
+
+ if (_hasTrackedProperty && actions.Any(action => HasTrackedProperty(action.TrackedProperties))
+ || !_hasTrackedProperty)
+ {
+ var logicAppRun = LogicAppConverter.ToLogicAppRun(workFlowRun, actions);
+ logicAppRuns.Add(logicAppRun);
+ }
+ }
+
+ _logger.LogTrace("Query resulted in {LogicAppRunCount} logic app runs", logicAppRuns.Count);
+ return logicAppRuns.AsEnumerable();
+ }
+ }
+
+ private async Task> FindLogicAppRunActionsAsync(ILogicManagementClient managementClient, string runName)
+ {
+ _logger.LogTrace("Find related logic app run actions...");
+ IPage workflowRunActions =
+ await managementClient.WorkflowRunActions.ListAsync(_resourceGroup, _logicAppName, runName);
+
+ var actions = new Collection();
+ foreach (WorkflowRunAction workflowRunAction in workflowRunActions)
+ {
+ JToken input = await GetHttpJsonStringAsync(workflowRunAction.InputsLink?.Uri);
+ JToken output = await GetHttpJsonStringAsync(workflowRunAction.OutputsLink?.Uri);
+
+ var action = LogicAppConverter.ToLogicAppAction(workflowRunAction, input, output);
+ actions.Add(action);
+ }
+
+ _logger.LogTrace("Found {LogicAppActionsCount} logic app actions", actions.Count);
+ return actions.AsEnumerable();
+ }
+
+ private static async Task GetHttpJsonStringAsync(string uri)
+ {
+ if (uri != null)
+ {
+ string json = await HttpClient.GetStringAsync(uri);
+ return JToken.Parse(json);
+ }
+
+ return null;
+ }
+
+ private bool HasTrackedProperty(IDictionary properties)
+ {
+ if (properties is null || properties.Count <= 0)
+ {
+ return false;
+ }
+
+ return properties.Any(property =>
+ {
+ if (property.Key is null || property.Value is null)
+ {
+ return false;
+ }
+
+ return property.Key.Equals(_trackedPropertyName, StringComparison.OrdinalIgnoreCase)
+ && property.Value.Equals(_trackedPropertyValue, StringComparison.OrdinalIgnoreCase);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Invictus.Testing/LogicAuthentication.cs b/src/Invictus.Testing/LogicAuthentication.cs
new file mode 100644
index 0000000..30390a4
--- /dev/null
+++ b/src/Invictus.Testing/LogicAuthentication.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Threading.Tasks;
+using GuardNet;
+using Microsoft.Azure.Management.Logic;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+using Microsoft.Rest;
+
+namespace Invictus.Testing
+{
+ ///
+ /// Authentication representation to authenticate with logic apps running on Azure.
+ ///
+ public class LogicAuthentication
+ {
+ private readonly Func> _authenticateAsync;
+
+ private LogicAuthentication(Func> authenticateAsync)
+ {
+ Guard.NotNull(authenticateAsync, nameof(authenticateAsync));
+
+ _authenticateAsync = authenticateAsync;
+ }
+
+ ///
+ /// Uses the service principal to authenticate with Azure.
+ ///
+ /// The ID where the resources are located on Azure.
+ /// The ID that identifies the subscription on Azure.
+ /// The ID of the client or application that has access to the logic apps running on Azure.
+ /// The secret of the client or application that has access to the logic apps running on Azure.
+ public static LogicAuthentication UsingServicePrincipal(string tenantId, string subscriptionId, string clientId, string clientSecret)
+ {
+ Guard.NotNullOrWhitespace(tenantId, nameof(tenantId));
+ Guard.NotNullOrWhitespace(subscriptionId, nameof(subscriptionId));
+ Guard.NotNullOrWhitespace(clientId, nameof(clientId));
+ Guard.NotNullOrWhitespace(clientSecret, nameof(clientSecret));
+
+ return new LogicAuthentication(
+ () => AuthenticateLogicAppsManagementAsync(subscriptionId, tenantId, clientId, clientSecret));
+ }
+
+ ///
+ /// Authenticate with Azure with the previously chosen authentication mechanism.
+ ///
+ ///
+ /// The management client to interact with logic app resources running on Azure.
+ ///
+ public async Task AuthenticateAsync()
+ {
+ return await _authenticateAsync();
+ }
+
+ private static async Task AuthenticateLogicAppsManagementAsync(string subscriptionId, string tenantId, string clientId, string clientSecret)
+ {
+ string authority = $"https://login.windows.net/{tenantId}";
+
+ var authContext = new AuthenticationContext(authority);
+ var credential = new ClientCredential(clientId, clientSecret);
+
+ AuthenticationResult token = await authContext.AcquireTokenAsync("https://management.azure.com/", credential);
+
+ return new LogicManagementClient(new TokenCredentials(token.AccessToken))
+ {
+ SubscriptionId = subscriptionId
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Invictus.Testing/Model/LogicApp.cs b/src/Invictus.Testing/Model/LogicApp.cs
index 83d5384..df69c44 100644
--- a/src/Invictus.Testing/Model/LogicApp.cs
+++ b/src/Invictus.Testing/Model/LogicApp.cs
@@ -14,19 +14,5 @@ public class LogicApp
public string Version { get; set; }
public string AccessEndpoint { get; set; }
public dynamic Definition { get; set; }
-
- public static explicit operator LogicApp(Workflow workflow)
- {
- return new LogicApp
- {
- Name = workflow.Name,
- CreatedTime = workflow.CreatedTime,
- ChangedTime = workflow.ChangedTime,
- State = workflow.State,
- Version = workflow.Version,
- AccessEndpoint = workflow.AccessEndpoint,
- Definition = workflow.Definition
- };
- }
}
}
diff --git a/src/Invictus.Testing/Model/LogicAppAction.cs b/src/Invictus.Testing/Model/LogicAppAction.cs
index d05dd26..a1f6d96 100644
--- a/src/Invictus.Testing/Model/LogicAppAction.cs
+++ b/src/Invictus.Testing/Model/LogicAppAction.cs
@@ -16,20 +16,5 @@ public class LogicAppAction
public dynamic Inputs { get; set; }
public dynamic Outputs { get; set; }
public Dictionary TrackedProperties { get; set; }
-
- public static explicit operator LogicAppAction(WorkflowRunAction workflowRunAction)
- {
- return new LogicAppAction
- {
- Name = workflowRunAction.Name,
- StartTime = workflowRunAction.StartTime,
- EndTime = workflowRunAction.EndTime,
- Status = workflowRunAction.Status,
- Error = workflowRunAction.Error,
- TrackedProperties = workflowRunAction.TrackedProperties != null
- ? JsonConvert.DeserializeObject>(workflowRunAction.TrackedProperties.ToString())
- : null
- };
- }
}
}
diff --git a/src/Invictus.Testing/Model/LogicAppRun.cs b/src/Invictus.Testing/Model/LogicAppRun.cs
index 5b800dc..74214c0 100644
--- a/src/Invictus.Testing/Model/LogicAppRun.cs
+++ b/src/Invictus.Testing/Model/LogicAppRun.cs
@@ -14,21 +14,7 @@ public class LogicAppRun
public object Error { get; set; }
public string CorrelationId { get; set; }
public LogicAppTrigger Trigger { get; set; }
- public List Actions { get; set; }
- public Dictionary TrackedProperties { get; set; }
-
- public static explicit operator LogicAppRun(WorkflowRun workFlowRun)
- {
- return new LogicAppRun
- {
- Id = workFlowRun.Name,
- StartTime = workFlowRun.StartTime,
- EndTime = workFlowRun.EndTime,
- Status = workFlowRun.Status,
- Error = workFlowRun.Error,
- CorrelationId = workFlowRun.Correlation?.ClientTrackingId,
- Trigger = (LogicAppTrigger)workFlowRun.Trigger
- };
- }
+ public IEnumerable Actions { get; set; }
+ public IReadOnlyDictionary TrackedProperties { get; set; }
}
}
diff --git a/src/Invictus.Testing/Model/LogicAppTrigger.cs b/src/Invictus.Testing/Model/LogicAppTrigger.cs
index 74805b5..3721ca2 100644
--- a/src/Invictus.Testing/Model/LogicAppTrigger.cs
+++ b/src/Invictus.Testing/Model/LogicAppTrigger.cs
@@ -14,19 +14,5 @@ public class LogicAppTrigger
public DateTimeOffset? EndTime { get; set; }
public string Status { get; set; }
public object Error { get; set; }
-
- public static explicit operator LogicAppTrigger(WorkflowRunTrigger workflowRunTrigger)
- {
- return new LogicAppTrigger
- {
- Name = workflowRunTrigger.Name,
- Inputs = workflowRunTrigger.Inputs,
- Outputs = workflowRunTrigger.Outputs,
- StartTime = workflowRunTrigger.StartTime,
- EndTime = workflowRunTrigger.EndTime,
- Status = workflowRunTrigger.Status,
- Error = workflowRunTrigger.Error,
- };
- }
}
}
diff --git a/src/Invictus.Testing/TimeoutTracker.cs b/src/Invictus.Testing/TimeoutTracker.cs
deleted file mode 100644
index e949dae..0000000
--- a/src/Invictus.Testing/TimeoutTracker.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Invictus.Testing
-{
- public struct TimeoutTracker
- {
- private int _total;
- private int _start;
-
- public TimeoutTracker(TimeSpan timeout)
- {
- long ltm = (long)timeout.TotalMilliseconds;
- if (ltm < -1 || ltm > (long)int.MaxValue)
- throw new ArgumentOutOfRangeException(nameof(timeout));
- _total = (int)ltm;
- if (_total != -1 && _total != 0)
- _start = Environment.TickCount;
- else
- _start = 0;
- }
-
- public TimeoutTracker(int millisecondsTimeout)
- {
- if (millisecondsTimeout < -1)
- throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));
- _total = millisecondsTimeout;
- if (_total != -1 && _total != 0)
- _start = Environment.TickCount;
- else
- _start = 0;
- }
-
- public int RemainingMilliseconds
- {
- get
- {
- if (_total == -1 || _total == 0)
- return _total;
-
- int elapsed = Environment.TickCount - _start;
- // elapsed may be negative if TickCount has overflowed by 2^31 milliseconds.
- if (elapsed < 0 || elapsed >= _total)
- return 0;
-
- return _total - elapsed;
- }
- }
-
- public bool IsExpired
- {
- get
- {
- return RemainingMilliseconds == 0;
- }
- }
- }
-}