diff --git a/src/Abstractions/TaskOptions.cs b/src/Abstractions/TaskOptions.cs index 7c0d54ee..49e98edf 100644 --- a/src/Abstractions/TaskOptions.cs +++ b/src/Abstractions/TaskOptions.cs @@ -176,6 +176,7 @@ public StartOrchestrationOptions(StartOrchestrationOptions options) this.Tags = options.Tags; this.Version = options.Version; this.DedupeStatuses = options.DedupeStatuses; + this.IdReusePolicy = options.IdReusePolicy; } /// @@ -204,6 +205,16 @@ public StartOrchestrationOptions(StartOrchestrationOptions options) /// /// /// For type-safe usage, use the WithDedupeStatuses extension method. + /// This property is mutually exclusive with IdReusePolicy. If both are set, IdReusePolicy takes precedence. /// public IReadOnlyList? DedupeStatuses { get; init; } + + /// + /// Gets the orchestration ID reuse policy. + /// + /// + /// This is an internal property. For type-safe usage, use the WithIdReusePolicy extension method. + /// This property takes precedence over DedupeStatuses if both are set. + /// + public object? IdReusePolicy { get; init; } } diff --git a/src/Client/Core/CreateOrchestrationAction.cs b/src/Client/Core/CreateOrchestrationAction.cs new file mode 100644 index 00000000..327815c9 --- /dev/null +++ b/src/Client/Core/CreateOrchestrationAction.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.DurableTask.Client; + +/// +/// Defines actions for handling orchestration instance ID conflicts. +/// +public enum CreateOrchestrationAction +{ + /// + /// Throws an exception if an orchestration instance with the specified ID already exists in one of the operation statuses. + /// This is the default behavior. + /// + Error = 0, + + /// + /// Ignores the request to create a new orchestration instance if one already exists in one of the operation statuses. + /// No exception is thrown and no new instance is created. + /// + Ignore = 1, + + /// + /// Terminates any existing orchestration instance with the same ID that is in one of the operation statuses, + /// and then creates a new instance as an atomic operation. This is similar to an on-demand ContinueAsNew. + /// + Terminate = 2, +} diff --git a/src/Client/Core/OrchestrationIdReusePolicy.cs b/src/Client/Core/OrchestrationIdReusePolicy.cs new file mode 100644 index 00000000..be03b2a4 --- /dev/null +++ b/src/Client/Core/OrchestrationIdReusePolicy.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.DurableTask.Client; + +/// +/// Defines a policy for reusing orchestration instance IDs. +/// +/// +/// This policy determines what happens when a client attempts to create a new orchestration instance +/// with an ID that already exists. The policy consists of an action (Error, Ignore, or Terminate) +/// and a set of orchestration runtime statuses to which the action applies. +/// +public sealed class OrchestrationIdReusePolicy +{ + /// + /// Initializes a new instance of the class. + /// + /// The orchestration runtime statuses to which the action applies. + /// The action to take when an orchestration instance with a matching status exists. + public OrchestrationIdReusePolicy( + IEnumerable operationStatuses, + CreateOrchestrationAction action) + { + Check.NotNull(operationStatuses); + this.OperationStatuses = operationStatuses.ToArray(); + this.Action = action; + } + + /// + /// Gets the orchestration runtime statuses to which the action applies. + /// + /// + /// When an orchestration instance exists with one of these statuses, the specified action will be taken. + /// For example, if the action is and the operation statuses + /// include , then any running instance with the same ID + /// will be terminated before creating a new instance. + /// + public IReadOnlyList OperationStatuses { get; } + + /// + /// Gets the action to take when an orchestration instance with a matching status exists. + /// + public CreateOrchestrationAction Action { get; } + + /// + /// Creates a policy that throws an error if an orchestration instance with the specified statuses already exists. + /// + /// The orchestration runtime statuses that should cause an error. + /// A new with the Error action. + public static OrchestrationIdReusePolicy Error(params OrchestrationRuntimeStatus[] statuses) + => new(statuses, CreateOrchestrationAction.Error); + + /// + /// Creates a policy that ignores the request if an orchestration instance with the specified statuses already exists. + /// + /// The orchestration runtime statuses that should cause the request to be ignored. + /// A new with the Ignore action. + public static OrchestrationIdReusePolicy Ignore(params OrchestrationRuntimeStatus[] statuses) + => new(statuses, CreateOrchestrationAction.Ignore); + + /// + /// Creates a policy that terminates any existing orchestration instance with the specified statuses and creates a new one. + /// + /// The orchestration runtime statuses that should be terminated before creating a new instance. + /// A new with the Terminate action. + public static OrchestrationIdReusePolicy Terminate(params OrchestrationRuntimeStatus[] statuses) + => new(statuses, CreateOrchestrationAction.Terminate); +} diff --git a/src/Client/Core/StartOrchestrationOptionsExtensions.cs b/src/Client/Core/StartOrchestrationOptionsExtensions.cs index 4bfac52f..660f8302 100644 --- a/src/Client/Core/StartOrchestrationOptionsExtensions.cs +++ b/src/Client/Core/StartOrchestrationOptionsExtensions.cs @@ -10,6 +10,9 @@ namespace Microsoft.DurableTask.Client; /// public static class StartOrchestrationOptionsExtensions { + /// + /// Gets the valid terminal orchestration statuses that can be used for deduplication and ID reuse policies. + /// public static readonly OrchestrationRuntimeStatus[] ValidDedupeStatuses = new[] { OrchestrationRuntimeStatus.Completed, @@ -33,4 +36,21 @@ public static StartOrchestrationOptions WithDedupeStatuses( DedupeStatuses = dedupeStatuses.Select(s => s.ToString()).ToList(), }; } + + /// + /// Creates a new with the specified orchestration ID reuse policy. + /// + /// The base options to extend. + /// The orchestration ID reuse policy. + /// A new instance with the ID reuse policy set. + public static StartOrchestrationOptions WithIdReusePolicy( + this StartOrchestrationOptions options, + OrchestrationIdReusePolicy policy) + { + Check.NotNull(policy); + return options with + { + IdReusePolicy = policy, + }; + } } diff --git a/src/Client/Grpc/GrpcDurableTaskClient.cs b/src/Client/Grpc/GrpcDurableTaskClient.cs index b6a6c72d..b5532cdd 100644 --- a/src/Client/Grpc/GrpcDurableTaskClient.cs +++ b/src/Client/Grpc/GrpcDurableTaskClient.cs @@ -123,11 +123,20 @@ public override async Task ScheduleNewOrchestrationInstanceAsync( request.ScheduledStartTimestamp = Timestamp.FromDateTimeOffset(startAt.Value.ToUniversalTime()); } - // Set orchestration ID reuse policy for deduplication support - // Note: This requires the protobuf to support OrchestrationIdReusePolicy field - // If the protobuf doesn't support it yet, this will need to be updated when the protobuf is updated - if (options?.DedupeStatuses != null && options.DedupeStatuses.Count > 0) + // Set orchestration ID reuse policy + // Priority: IdReusePolicy > DedupeStatuses + if (options?.IdReusePolicy is OrchestrationIdReusePolicy idReusePolicy) { + // Use the new explicit ID reuse policy + P.OrchestrationIdReusePolicy? policy = ProtoUtils.ConvertToProtoReusePolicy(idReusePolicy); + if (policy != null) + { + request.OrchestrationIdReusePolicy = policy; + } + } + else if (options?.DedupeStatuses != null && options.DedupeStatuses.Count > 0) + { + // Fall back to legacy dedupe statuses for backward compatibility // Parse and validate all status strings to enum first ImmutableHashSet dedupeStatuses = options.DedupeStatuses .Select(s => diff --git a/src/Client/Grpc/ProtoUtils.cs b/src/Client/Grpc/ProtoUtils.cs index f307f43f..65dd6af3 100644 --- a/src/Client/Grpc/ProtoUtils.cs +++ b/src/Client/Grpc/ProtoUtils.cs @@ -12,6 +12,42 @@ namespace Microsoft.DurableTask.Client.Grpc; /// public static class ProtoUtils { + /// + /// Converts a public CreateOrchestrationAction to a protobuf CreateOrchestrationAction. + /// + /// The public action. + /// A protobuf CreateOrchestrationAction. + internal static P.CreateOrchestrationAction ConvertToProtoAction( + Microsoft.DurableTask.Client.CreateOrchestrationAction action) + => action switch + { + Microsoft.DurableTask.Client.CreateOrchestrationAction.Error => P.CreateOrchestrationAction.Error, + Microsoft.DurableTask.Client.CreateOrchestrationAction.Ignore => P.CreateOrchestrationAction.Ignore, + Microsoft.DurableTask.Client.CreateOrchestrationAction.Terminate => P.CreateOrchestrationAction.Terminate, + _ => throw new ArgumentOutOfRangeException(nameof(action), "Unexpected value"), + }; + +#pragma warning disable 0618 // Referencing Obsolete member. This is intention as we are only converting it. + /// + /// Converts to . + /// + /// The orchestration status. + /// A . + internal static P.OrchestrationStatus ToGrpcStatus(this OrchestrationRuntimeStatus status) + => status switch + { + OrchestrationRuntimeStatus.Canceled => P.OrchestrationStatus.Canceled, + OrchestrationRuntimeStatus.Completed => P.OrchestrationStatus.Completed, + OrchestrationRuntimeStatus.ContinuedAsNew => P.OrchestrationStatus.ContinuedAsNew, + OrchestrationRuntimeStatus.Failed => P.OrchestrationStatus.Failed, + OrchestrationRuntimeStatus.Pending => P.OrchestrationStatus.Pending, + OrchestrationRuntimeStatus.Running => P.OrchestrationStatus.Running, + OrchestrationRuntimeStatus.Terminated => P.OrchestrationStatus.Terminated, + OrchestrationRuntimeStatus.Suspended => P.OrchestrationStatus.Suspended, + _ => throw new ArgumentOutOfRangeException(nameof(status), "Unexpected value"), + }; +#pragma warning restore 0618 // Referencing Obsolete member. + /// /// Gets the terminal orchestration statuses that are commonly used for deduplication. /// These are the statuses that can be used in OrchestrationIdReusePolicy. @@ -30,14 +66,15 @@ public static class ProtoUtils /// /// Converts dedupe statuses (statuses that should NOT be replaced) to an OrchestrationIdReusePolicy - /// with replaceable statuses (statuses that CAN be replaced). + /// with TERMINATE action for terminal statuses that CAN be replaced. /// /// The orchestration statuses that should NOT be replaced. These are statuses for which an exception should be thrown if an orchestration already exists. - /// An OrchestrationIdReusePolicy with replaceable statuses set, or null if all terminal statuses are dedupe statuses. + /// An OrchestrationIdReusePolicy with TERMINATE action and operation statuses set, or null if all terminal statuses are dedupe statuses. /// - /// The policy uses "replaceableStatus" - these are statuses that CAN be replaced. - /// dedupeStatuses are statuses that should NOT be replaced. - /// So replaceableStatus = all terminal statuses MINUS dedupeStatuses. + /// This method maintains backward compatibility by converting dedupe statuses to the new policy format. + /// The policy will have action = TERMINATE and operationStatus = terminal statuses that can be replaced. + /// dedupeStatuses are statuses that should NOT be replaced (ERROR action). + /// So operationStatus = all terminal statuses MINUS dedupeStatuses. /// public static P.OrchestrationIdReusePolicy? ConvertDedupeStatusesToReusePolicy( IEnumerable? dedupeStatuses) @@ -45,67 +82,75 @@ public static class ProtoUtils ImmutableArray terminalStatuses = GetTerminalStatuses(); ImmutableHashSet dedupeStatusSet = dedupeStatuses?.ToImmutableHashSet() ?? ImmutableHashSet.Empty; - P.OrchestrationIdReusePolicy policy = new(); + P.OrchestrationIdReusePolicy policy = new() + { + Action = P.CreateOrchestrationAction.Terminate, + }; - // Add terminal statuses that are NOT in dedupeStatuses as replaceable + // Add terminal statuses that are NOT in dedupeStatuses to operation status (these can be terminated and replaced) foreach (P.OrchestrationStatus terminalStatus in terminalStatuses.Where(status => !dedupeStatusSet.Contains(status))) { - policy.ReplaceableStatus.Add(terminalStatus); + policy.OperationStatus.Add(terminalStatus); + } + + // Only return policy if we have operation statuses + return policy.OperationStatus.Count > 0 ? policy : null; + } + + /// + /// Converts a public OrchestrationIdReusePolicy to a protobuf OrchestrationIdReusePolicy. + /// + /// The public orchestration ID reuse policy. + /// A protobuf OrchestrationIdReusePolicy. + public static P.OrchestrationIdReusePolicy? ConvertToProtoReusePolicy( + Microsoft.DurableTask.Client.OrchestrationIdReusePolicy? policy) + { + if (policy == null) + { + return null; + } + + P.OrchestrationIdReusePolicy protoPolicy = new() + { + Action = ConvertToProtoAction(policy.Action), + }; + + foreach (OrchestrationRuntimeStatus status in policy.OperationStatuses) + { + protoPolicy.OperationStatus.Add(status.ToGrpcStatus()); } - // Only return policy if we have replaceable statuses - return policy.ReplaceableStatus.Count > 0 ? policy : null; + return protoPolicy; } /// - /// Converts an OrchestrationIdReusePolicy with replaceable statuses to dedupe statuses - /// (statuses that should NOT be replaced). + /// Converts an OrchestrationIdReusePolicy to dedupe statuses (statuses that should NOT be replaced). /// - /// The OrchestrationIdReusePolicy containing replaceable statuses. - /// An array of orchestration statuses that should NOT be replaced, or null if all terminal statuses are replaceable. + /// The OrchestrationIdReusePolicy containing action and operation statuses. + /// An array of orchestration statuses that should NOT be replaced, or null if all terminal statuses can be replaced. /// - /// The policy uses "replaceableStatus" - these are statuses that CAN be replaced. - /// dedupeStatuses are statuses that should NOT be replaced (should throw exception). - /// So dedupeStatuses = all terminal statuses MINUS replaceableStatus. + /// This method maintains backward compatibility by converting the new policy format to dedupe statuses. + /// For TERMINATE action: dedupeStatuses = all terminal statuses MINUS operationStatus. + /// For ERROR or IGNORE action: the behavior depends on the action semantics. /// public static P.OrchestrationStatus[]? ConvertReusePolicyToDedupeStatuses( P.OrchestrationIdReusePolicy? policy) { - if (policy == null || policy.ReplaceableStatus.Count == 0) + if (policy == null || policy.OperationStatus.Count == 0) { return null; } ImmutableArray terminalStatuses = GetTerminalStatuses(); - ImmutableHashSet replaceableStatusSet = policy.ReplaceableStatus.ToImmutableHashSet(); + ImmutableHashSet operationStatusSet = policy.OperationStatus.ToImmutableHashSet(); - // Calculate dedupe statuses = terminal statuses - replaceable statuses + // For TERMINATE action: dedupe statuses = terminal statuses - operation status + // For other actions, the conversion may not be straightforward P.OrchestrationStatus[] dedupeStatuses = terminalStatuses - .Where(terminalStatus => !replaceableStatusSet.Contains(terminalStatus)) + .Where(terminalStatus => !operationStatusSet.Contains(terminalStatus)) .ToArray(); // Only return if there are dedupe statuses return dedupeStatuses.Length > 0 ? dedupeStatuses : null; } - -#pragma warning disable 0618 // Referencing Obsolete member. This is intention as we are only converting it. - /// - /// Converts to . - /// - /// The orchestration status. - /// A . - internal static P.OrchestrationStatus ToGrpcStatus(this OrchestrationRuntimeStatus status) - => status switch - { - OrchestrationRuntimeStatus.Canceled => P.OrchestrationStatus.Canceled, - OrchestrationRuntimeStatus.Completed => P.OrchestrationStatus.Completed, - OrchestrationRuntimeStatus.ContinuedAsNew => P.OrchestrationStatus.ContinuedAsNew, - OrchestrationRuntimeStatus.Failed => P.OrchestrationStatus.Failed, - OrchestrationRuntimeStatus.Pending => P.OrchestrationStatus.Pending, - OrchestrationRuntimeStatus.Running => P.OrchestrationStatus.Running, - OrchestrationRuntimeStatus.Terminated => P.OrchestrationStatus.Terminated, - OrchestrationRuntimeStatus.Suspended => P.OrchestrationStatus.Suspended, - _ => throw new ArgumentOutOfRangeException(nameof(status), "Unexpected value"), - }; -#pragma warning restore 0618 // Referencing Obsolete member. } diff --git a/src/Grpc/orchestrator_service.proto b/src/Grpc/orchestrator_service.proto index 196c88da..ef3b6d2b 100644 --- a/src/Grpc/orchestrator_service.proto +++ b/src/Grpc/orchestrator_service.proto @@ -55,6 +55,12 @@ enum OrchestrationStatus { ORCHESTRATION_STATUS_SUSPENDED = 7; } +enum CreateOrchestrationAction { + CREATE_ORCHESTRATION_ACTION_ERROR = 0; + CREATE_ORCHESTRATION_ACTION_IGNORE = 1; + CREATE_ORCHESTRATION_ACTION_TERMINATE = 2; +} + message ParentInstanceInfo { int32 taskScheduledId = 1; google.protobuf.StringValue name = 2; @@ -380,8 +386,8 @@ message CreateInstanceRequest { } message OrchestrationIdReusePolicy { - repeated OrchestrationStatus replaceableStatus = 1; - reserved 2; + repeated OrchestrationStatus operationStatus = 1; + CreateOrchestrationAction action = 2; } message CreateInstanceResponse { diff --git a/test/Client/Grpc.Tests/OrchestrationIdReusePolicyTests.cs b/test/Client/Grpc.Tests/OrchestrationIdReusePolicyTests.cs new file mode 100644 index 00000000..cea195a7 --- /dev/null +++ b/test/Client/Grpc.Tests/OrchestrationIdReusePolicyTests.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.DurableTask.Client; +using P = Microsoft.DurableTask.Protobuf; + +namespace Microsoft.DurableTask.Client.Grpc.Tests; + +public class OrchestrationIdReusePolicyTests +{ + [Fact] + public void OrchestrationIdReusePolicy_Error_CreatesCorrectPolicy() + { + // Arrange & Act + OrchestrationIdReusePolicy policy = OrchestrationIdReusePolicy.Error( + OrchestrationRuntimeStatus.Running, + OrchestrationRuntimeStatus.Pending); + + // Assert + policy.Action.Should().Be(CreateOrchestrationAction.Error); + policy.OperationStatuses.Should().HaveCount(2); + policy.OperationStatuses.Should().Contain(OrchestrationRuntimeStatus.Running); + policy.OperationStatuses.Should().Contain(OrchestrationRuntimeStatus.Pending); + } + + [Fact] + public void OrchestrationIdReusePolicy_Ignore_CreatesCorrectPolicy() + { + // Arrange & Act + OrchestrationIdReusePolicy policy = OrchestrationIdReusePolicy.Ignore( + OrchestrationRuntimeStatus.Completed, + OrchestrationRuntimeStatus.Failed); + + // Assert + policy.Action.Should().Be(CreateOrchestrationAction.Ignore); + policy.OperationStatuses.Should().HaveCount(2); + policy.OperationStatuses.Should().Contain(OrchestrationRuntimeStatus.Completed); + policy.OperationStatuses.Should().Contain(OrchestrationRuntimeStatus.Failed); + } + + [Fact] + public void OrchestrationIdReusePolicy_Terminate_CreatesCorrectPolicy() + { + // Arrange & Act + OrchestrationIdReusePolicy policy = OrchestrationIdReusePolicy.Terminate( + OrchestrationRuntimeStatus.Running); + + // Assert + policy.Action.Should().Be(CreateOrchestrationAction.Terminate); + policy.OperationStatuses.Should().HaveCount(1); + policy.OperationStatuses.Should().Contain(OrchestrationRuntimeStatus.Running); + } + + [Fact] + public void ConvertToProtoReusePolicy_NullPolicy_ReturnsNull() + { + // Arrange + OrchestrationIdReusePolicy? policy = null; + + // Act + P.OrchestrationIdReusePolicy? result = ProtoUtils.ConvertToProtoReusePolicy(policy); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void ConvertToProtoReusePolicy_ErrorAction_ConvertsCorrectly() + { + // Arrange + OrchestrationIdReusePolicy policy = OrchestrationIdReusePolicy.Error( + OrchestrationRuntimeStatus.Running, + OrchestrationRuntimeStatus.Pending); + + // Act + P.OrchestrationIdReusePolicy? result = ProtoUtils.ConvertToProtoReusePolicy(policy); + + // Assert + result.Should().NotBeNull(); + result!.Action.Should().Be(P.CreateOrchestrationAction.Error); + result.OperationStatus.Should().HaveCount(2); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Running); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Pending); + } + + [Fact] + public void ConvertToProtoReusePolicy_IgnoreAction_ConvertsCorrectly() + { + // Arrange + OrchestrationIdReusePolicy policy = OrchestrationIdReusePolicy.Ignore( + OrchestrationRuntimeStatus.Completed); + + // Act + P.OrchestrationIdReusePolicy? result = ProtoUtils.ConvertToProtoReusePolicy(policy); + + // Assert + result.Should().NotBeNull(); + result!.Action.Should().Be(P.CreateOrchestrationAction.Ignore); + result.OperationStatus.Should().HaveCount(1); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Completed); + } + + [Fact] + public void ConvertToProtoReusePolicy_TerminateAction_ConvertsCorrectly() + { + // Arrange + OrchestrationIdReusePolicy policy = OrchestrationIdReusePolicy.Terminate( + OrchestrationRuntimeStatus.Failed, + OrchestrationRuntimeStatus.Terminated); + + // Act + P.OrchestrationIdReusePolicy? result = ProtoUtils.ConvertToProtoReusePolicy(policy); + + // Assert + result.Should().NotBeNull(); + result!.Action.Should().Be(P.CreateOrchestrationAction.Terminate); + result.OperationStatus.Should().HaveCount(2); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Failed); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Terminated); + } + + [Fact] + public void ConvertToProtoAction_AllActions_ConvertCorrectly() + { + // Assert - Error + ProtoUtils.ConvertToProtoAction(CreateOrchestrationAction.Error) + .Should().Be(P.CreateOrchestrationAction.Error); + + // Assert - Ignore + ProtoUtils.ConvertToProtoAction(CreateOrchestrationAction.Ignore) + .Should().Be(P.CreateOrchestrationAction.Ignore); + + // Assert - Terminate + ProtoUtils.ConvertToProtoAction(CreateOrchestrationAction.Terminate) + .Should().Be(P.CreateOrchestrationAction.Terminate); + } + + [Fact] + public void WithIdReusePolicy_SetsPolicy() + { + // Arrange + var options = new StartOrchestrationOptions(); + var policy = OrchestrationIdReusePolicy.Terminate(OrchestrationRuntimeStatus.Running); + + // Act + StartOrchestrationOptions result = options.WithIdReusePolicy(policy); + + // Assert + result.IdReusePolicy.Should().Be(policy); + } +} diff --git a/test/Client/Grpc.Tests/ProtoUtilsTests.cs b/test/Client/Grpc.Tests/ProtoUtilsTests.cs index 4db7a884..50292d94 100644 --- a/test/Client/Grpc.Tests/ProtoUtilsTests.cs +++ b/test/Client/Grpc.Tests/ProtoUtilsTests.cs @@ -47,7 +47,7 @@ public void ConvertDedupeStatusesToReusePolicy_EmptyArray_ReturnsPolicyWithAllTe // Assert // Empty array means no dedupe statuses, so all terminal statuses are replaceable result.Should().NotBeNull(); - result!.ReplaceableStatus.Should().HaveCount(4); + result!.OperationStatus.Should().HaveCount(4); } [Fact] @@ -76,12 +76,12 @@ public void ConvertDedupeStatusesToReusePolicy_NoDedupeStatuses_ReturnsPolicyWit // Assert // When no dedupe statuses, all terminal statuses should be replaceable result.Should().NotBeNull(); - result!.ReplaceableStatus.Should().HaveCount(4); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Completed); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Failed); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Terminated); + result!.OperationStatus.Should().HaveCount(4); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Completed); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Failed); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Terminated); #pragma warning disable CS0618 // Type or member is obsolete - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Canceled); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Canceled); #pragma warning restore CS0618 } @@ -96,13 +96,13 @@ public void ConvertDedupeStatusesToReusePolicy_SingleDedupeStatus_ReturnsPolicyW // Assert result.Should().NotBeNull(); - result!.ReplaceableStatus.Should().HaveCount(3); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Failed); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Terminated); + result!.OperationStatus.Should().HaveCount(3); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Failed); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Terminated); #pragma warning disable CS0618 // Type or member is obsolete - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Canceled); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Canceled); #pragma warning restore CS0618 - result.ReplaceableStatus.Should().NotContain(P.OrchestrationStatus.Completed); + result.OperationStatus.Should().NotContain(P.OrchestrationStatus.Completed); } [Fact] @@ -120,13 +120,13 @@ public void ConvertDedupeStatusesToReusePolicy_MultipleDedupeStatuses_ReturnsPol // Assert result.Should().NotBeNull(); - result!.ReplaceableStatus.Should().HaveCount(2); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Terminated); + result!.OperationStatus.Should().HaveCount(2); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Terminated); #pragma warning disable CS0618 // Type or member is obsolete - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Canceled); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Canceled); #pragma warning restore CS0618 - result.ReplaceableStatus.Should().NotContain(P.OrchestrationStatus.Completed); - result.ReplaceableStatus.Should().NotContain(P.OrchestrationStatus.Failed); + result.OperationStatus.Should().NotContain(P.OrchestrationStatus.Completed); + result.OperationStatus.Should().NotContain(P.OrchestrationStatus.Failed); } [Fact] @@ -145,10 +145,10 @@ public void ConvertDedupeStatusesToReusePolicy_DuplicateDedupeStatuses_HandlesDu // Assert result.Should().NotBeNull(); - result!.ReplaceableStatus.Should().HaveCount(2); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Terminated); + result!.OperationStatus.Should().HaveCount(2); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Terminated); #pragma warning disable CS0618 // Type or member is obsolete - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Canceled); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Canceled); #pragma warning restore CS0618 } @@ -168,13 +168,13 @@ public void ConvertDedupeStatusesToReusePolicy_NonTerminalStatus_IgnoresNonTermi // Assert result.Should().NotBeNull(); - result!.ReplaceableStatus.Should().HaveCount(3); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Failed); - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Terminated); + result!.OperationStatus.Should().HaveCount(3); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Failed); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Terminated); #pragma warning disable CS0618 // Type or member is obsolete - result.ReplaceableStatus.Should().Contain(P.OrchestrationStatus.Canceled); + result.OperationStatus.Should().Contain(P.OrchestrationStatus.Canceled); #pragma warning restore CS0618 - result.ReplaceableStatus.Should().NotContain(P.OrchestrationStatus.Completed); + result.OperationStatus.Should().NotContain(P.OrchestrationStatus.Completed); } [Fact] @@ -211,7 +211,7 @@ public void ConvertReusePolicyToDedupeStatuses_AllTerminalStatusesReplaceable_Re ImmutableArray terminalStatuses = ProtoUtils.GetTerminalStatuses(); foreach (var status in terminalStatuses) { - policy.ReplaceableStatus.Add(status); + policy.OperationStatus.Add(status); } // Act @@ -222,11 +222,11 @@ public void ConvertReusePolicyToDedupeStatuses_AllTerminalStatusesReplaceable_Re } [Fact] - public void ConvertReusePolicyToDedupeStatuses_SingleReplaceableStatus_ReturnsRemainingStatuses() + public void ConvertReusePolicyToDedupeStatuses_SingleOperationStatus_ReturnsRemainingStatuses() { // Arrange var policy = new P.OrchestrationIdReusePolicy(); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Completed); + policy.OperationStatus.Add(P.OrchestrationStatus.Completed); // Act P.OrchestrationStatus[]? result = ProtoUtils.ConvertReusePolicyToDedupeStatuses(policy); @@ -243,12 +243,12 @@ public void ConvertReusePolicyToDedupeStatuses_SingleReplaceableStatus_ReturnsRe } [Fact] - public void ConvertReusePolicyToDedupeStatuses_MultipleReplaceableStatuses_ReturnsRemainingStatuses() + public void ConvertReusePolicyToDedupeStatuses_MultipleOperationStatuses_ReturnsRemainingStatuses() { // Arrange var policy = new P.OrchestrationIdReusePolicy(); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Completed); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Failed); + policy.OperationStatus.Add(P.OrchestrationStatus.Completed); + policy.OperationStatus.Add(P.OrchestrationStatus.Failed); // Act P.OrchestrationStatus[]? result = ProtoUtils.ConvertReusePolicyToDedupeStatuses(policy); @@ -269,9 +269,9 @@ public void ConvertReusePolicyToDedupeStatuses_NonTerminalStatusInPolicy_Ignores { // Arrange var policy = new P.OrchestrationIdReusePolicy(); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Completed); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Running); // Non-terminal status - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Pending); // Non-terminal status + policy.OperationStatus.Add(P.OrchestrationStatus.Completed); + policy.OperationStatus.Add(P.OrchestrationStatus.Running); // Non-terminal status + policy.OperationStatus.Add(P.OrchestrationStatus.Pending); // Non-terminal status // Act P.OrchestrationStatus[]? result = ProtoUtils.ConvertReusePolicyToDedupeStatuses(policy); @@ -288,13 +288,13 @@ public void ConvertReusePolicyToDedupeStatuses_NonTerminalStatusInPolicy_Ignores } [Fact] - public void ConvertReusePolicyToDedupeStatuses_DuplicateReplaceableStatuses_HandlesDuplicates() + public void ConvertReusePolicyToDedupeStatuses_DuplicateOperationStatuses_HandlesDuplicates() { // Arrange var policy = new P.OrchestrationIdReusePolicy(); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Completed); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Completed); // Duplicate - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Failed); + policy.OperationStatus.Add(P.OrchestrationStatus.Completed); + policy.OperationStatus.Add(P.OrchestrationStatus.Completed); // Duplicate + policy.OperationStatus.Add(P.OrchestrationStatus.Failed); // Act P.OrchestrationStatus[]? result = ProtoUtils.ConvertReusePolicyToDedupeStatuses(policy); @@ -332,8 +332,8 @@ public void ConvertReusePolicyToDedupeStatuses_ThenConvertBack_ReturnsOriginalPo { // Arrange var policy = new P.OrchestrationIdReusePolicy(); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Completed); - policy.ReplaceableStatus.Add(P.OrchestrationStatus.Failed); + policy.OperationStatus.Add(P.OrchestrationStatus.Completed); + policy.OperationStatus.Add(P.OrchestrationStatus.Failed); // Act P.OrchestrationStatus[]? dedupeStatuses = ProtoUtils.ConvertReusePolicyToDedupeStatuses(policy); @@ -341,7 +341,7 @@ public void ConvertReusePolicyToDedupeStatuses_ThenConvertBack_ReturnsOriginalPo // Assert convertedBack.Should().NotBeNull(); - convertedBack!.ReplaceableStatus.Should().BeEquivalentTo(policy.ReplaceableStatus); + convertedBack!.OperationStatus.Should().BeEquivalentTo(policy.OperationStatus); } [Fact] @@ -368,7 +368,7 @@ public void ConvertReusePolicyToDedupeStatuses_AllStatuses_ThenConvertBack_Retur ImmutableArray terminalStatuses = ProtoUtils.GetTerminalStatuses(); foreach (var status in terminalStatuses) { - policy.ReplaceableStatus.Add(status); + policy.OperationStatus.Add(status); } // Act @@ -380,8 +380,8 @@ public void ConvertReusePolicyToDedupeStatuses_AllStatuses_ThenConvertBack_Retur // null dedupe statuses -> all are replaceable -> policy with all statuses dedupeStatuses.Should().BeNull(); convertedBack.Should().NotBeNull(); - convertedBack!.ReplaceableStatus.Should().HaveCount(4); - convertedBack.ReplaceableStatus.Should().BeEquivalentTo(policy.ReplaceableStatus); + convertedBack!.OperationStatus.Should().HaveCount(4); + convertedBack.OperationStatus.Should().BeEquivalentTo(policy.OperationStatus); } [Fact] @@ -416,7 +416,7 @@ public void ConvertReusePolicyToDedupeStatuses_EmptyPolicy_ThenConvertBack_Retur // null dedupe statuses -> all terminal statuses are replaceable -> policy with all statuses dedupeStatuses.Should().BeNull(); convertedBack.Should().NotBeNull(); - convertedBack!.ReplaceableStatus.Should().HaveCount(4); + convertedBack!.OperationStatus.Should().HaveCount(4); } [Theory]