File: Channels\TransactionFlowBindingElementTest.cs
Web Access
Project: ..\..\..\src\System.ServiceModel.Primitives\tests\System.ServiceModel.Primitives.Tests.csproj (System.ServiceModel.Primitives.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using Infrastructure.Common;
using Xunit;
 
public static class TransactionFlowBindingElementTest
{
    // ===== Default constructor tests =====
 
    [WcfFact]
    public static void Default_Ctor_Initializes_TransactionProtocol_To_OleTransactions()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        Assert.Equal(TransactionProtocol.OleTransactions, bindingElement.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Default_Ctor_Initializes_AllowWildcardAction_To_False()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        Assert.False(bindingElement.AllowWildcardAction);
    }
 
    // ===== Constructor with TransactionProtocol parameter tests =====
 
    [WcfFact]
    public static void Ctor_With_OleTransactions_Sets_TransactionProtocol()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.OleTransactions);
        Assert.Equal(TransactionProtocol.OleTransactions, bindingElement.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Ctor_With_WSAtomicTransactionOctober2004_Sets_TransactionProtocol()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransactionOctober2004);
        Assert.Equal(TransactionProtocol.WSAtomicTransactionOctober2004, bindingElement.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Ctor_With_WSAtomicTransaction11_Sets_TransactionProtocol()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransaction11);
        Assert.Equal(TransactionProtocol.WSAtomicTransaction11, bindingElement.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Ctor_With_Null_TransactionProtocol_Throws()
    {
        Assert.ThrowsAny<Exception>(() => new TransactionFlowBindingElement(null));
    }
 
    // ===== TransactionProtocol property setter tests =====
 
    [WcfFact]
    public static void TransactionProtocol_Property_Sets_OleTransactions()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        bindingElement.TransactionProtocol = TransactionProtocol.OleTransactions;
        Assert.Equal(TransactionProtocol.OleTransactions, bindingElement.TransactionProtocol);
    }
 
    [WcfFact]
    public static void TransactionProtocol_Property_Sets_WSAtomicTransactionOctober2004()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        bindingElement.TransactionProtocol = TransactionProtocol.WSAtomicTransactionOctober2004;
        Assert.Equal(TransactionProtocol.WSAtomicTransactionOctober2004, bindingElement.TransactionProtocol);
    }
 
    [WcfFact]
    public static void TransactionProtocol_Property_Sets_WSAtomicTransaction11()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        bindingElement.TransactionProtocol = TransactionProtocol.WSAtomicTransaction11;
        Assert.Equal(TransactionProtocol.WSAtomicTransaction11, bindingElement.TransactionProtocol);
    }
 
    [WcfFact]
    public static void TransactionProtocol_Property_Set_Null_Throws()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        Assert.Throws<ArgumentOutOfRangeException>(() => bindingElement.TransactionProtocol = null);
    }
 
    // ===== AllowWildcardAction property setter tests =====
 
    [WcfTheory]
    [InlineData(true)]
    [InlineData(false)]
    public static void AllowWildcardAction_Property_Sets(bool value)
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        bindingElement.AllowWildcardAction = value;
        Assert.Equal(value, bindingElement.AllowWildcardAction);
    }
 
    // ===== Clone tests =====
 
    [WcfFact]
    public static void Clone_Returns_New_Instance_With_Same_Properties()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransaction11);
        original.AllowWildcardAction = true;
 
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.NotSame(original, clone);
        Assert.Equal(original.TransactionProtocol, clone.TransactionProtocol);
        Assert.Equal(original.AllowWildcardAction, clone.AllowWildcardAction);
    }
 
    [WcfFact]
    public static void Clone_With_OleTransactions_Preserves_Protocol()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.OleTransactions);
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.NotSame(original, clone);
        Assert.Equal(TransactionProtocol.OleTransactions, clone.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Clone_With_WSAtomicTransactionOctober2004_Preserves_Protocol()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransactionOctober2004);
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.NotSame(original, clone);
        Assert.Equal(TransactionProtocol.WSAtomicTransactionOctober2004, clone.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Clone_With_OleTransactions_And_AllowWildcardAction_Preserves_Both()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.OleTransactions);
        original.AllowWildcardAction = true;
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.Equal(TransactionProtocol.OleTransactions, clone.TransactionProtocol);
        Assert.True(clone.AllowWildcardAction);
    }
 
    [WcfFact]
    public static void Clone_With_WSAtomicTransactionOctober2004_And_AllowWildcardAction_Preserves_Both()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransactionOctober2004);
        original.AllowWildcardAction = true;
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.Equal(TransactionProtocol.WSAtomicTransactionOctober2004, clone.TransactionProtocol);
        Assert.True(clone.AllowWildcardAction);
    }
 
    [WcfFact]
    public static void Clone_With_WSAtomicTransaction11_And_AllowWildcardAction_Preserves_Both()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransaction11);
        original.AllowWildcardAction = true;
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.Equal(TransactionProtocol.WSAtomicTransaction11, clone.TransactionProtocol);
        Assert.True(clone.AllowWildcardAction);
    }
 
    [WcfFact]
    public static void Clone_After_Protocol_Change_Preserves_New_Protocol()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.OleTransactions);
        original.TransactionProtocol = TransactionProtocol.WSAtomicTransaction11;
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.Equal(TransactionProtocol.WSAtomicTransaction11, clone.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Clone_After_Protocol_Change_From_WSAt11_To_OleTx()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransaction11);
        original.TransactionProtocol = TransactionProtocol.OleTransactions;
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.Equal(TransactionProtocol.OleTransactions, clone.TransactionProtocol);
    }
 
    [WcfFact]
    public static void Clone_After_Protocol_Change_From_OleTx_To_WSAtOct2004()
    {
        TransactionFlowBindingElement original = new TransactionFlowBindingElement(TransactionProtocol.OleTransactions);
        original.TransactionProtocol = TransactionProtocol.WSAtomicTransactionOctober2004;
        TransactionFlowBindingElement clone = (TransactionFlowBindingElement)original.Clone();
 
        Assert.Equal(TransactionProtocol.WSAtomicTransactionOctober2004, clone.TransactionProtocol);
    }
 
    // ===== ShouldSerializeTransactionProtocol tests =====
 
    [WcfFact]
    public static void ShouldSerializeTransactionProtocol_Returns_False_For_Default()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        Assert.False(bindingElement.ShouldSerializeTransactionProtocol());
    }
 
    [WcfFact]
    public static void ShouldSerializeTransactionProtocol_Returns_True_For_NonDefault()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransaction11);
        Assert.True(bindingElement.ShouldSerializeTransactionProtocol());
    }
 
    [WcfFact]
    public static void ShouldSerializeTransactionProtocol_Returns_False_For_OleTransactions()
    {
        // OleTransactions is the default, so ShouldSerialize should return false
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.OleTransactions);
        Assert.False(bindingElement.ShouldSerializeTransactionProtocol());
    }
 
    [WcfFact]
    public static void ShouldSerializeTransactionProtocol_Returns_True_For_WSAtomicTransactionOctober2004()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransactionOctober2004);
        Assert.True(bindingElement.ShouldSerializeTransactionProtocol());
    }
 
    [WcfFact]
    public static void ShouldSerializeTransactionProtocol_Returns_True_After_Changing_From_Default()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        Assert.False(bindingElement.ShouldSerializeTransactionProtocol());
 
        bindingElement.TransactionProtocol = TransactionProtocol.WSAtomicTransaction11;
        Assert.True(bindingElement.ShouldSerializeTransactionProtocol());
    }
 
    [WcfFact]
    public static void ShouldSerializeTransactionProtocol_Returns_False_After_Changing_Back_To_Default()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransaction11);
        Assert.True(bindingElement.ShouldSerializeTransactionProtocol());
 
        bindingElement.TransactionProtocol = TransactionProtocol.OleTransactions;
        Assert.False(bindingElement.ShouldSerializeTransactionProtocol());
    }
 
    // ===== CanBuildChannelFactory tests =====
 
    [WcfFact]
    public static void CanBuildChannelFactory_Null_Context_Throws()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement();
        Assert.Throws<ArgumentNullException>(() => bindingElement.CanBuildChannelFactory<IOutputChannel>(null));
    }
 
    [WcfFact]
    public static void CanBuildChannelFactory_Null_Context_Throws_For_All_Protocols()
    {
        var protocols = new[]
        {
            TransactionProtocol.OleTransactions,
            TransactionProtocol.WSAtomicTransactionOctober2004,
            TransactionProtocol.WSAtomicTransaction11
        };
 
        foreach (var protocol in protocols)
        {
            TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(protocol);
            Assert.Throws<ArgumentNullException>(() => bindingElement.CanBuildChannelFactory<IOutputChannel>(null));
        }
    }
 
    // ===== BuildChannelFactory null context throws for all protocols =====
 
    [WcfFact]
    public static void BuildChannelFactory_Null_Context_Throws_For_OleTransactions()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.OleTransactions);
        Assert.ThrowsAny<ArgumentException>(() => bindingElement.BuildChannelFactory<IOutputChannel>(null));
    }
 
    [WcfFact]
    public static void BuildChannelFactory_Null_Context_Throws_For_WSAtomicTransactionOctober2004()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransactionOctober2004);
        Assert.ThrowsAny<ArgumentException>(() => bindingElement.BuildChannelFactory<IOutputChannel>(null));
    }
 
    [WcfFact]
    public static void BuildChannelFactory_Null_Context_Throws_For_WSAtomicTransaction11()
    {
        TransactionFlowBindingElement bindingElement = new TransactionFlowBindingElement(TransactionProtocol.WSAtomicTransaction11);
        Assert.ThrowsAny<ArgumentException>(() => bindingElement.BuildChannelFactory<IOutputChannel>(null));
    }
 
    // ===== TransactionProtocol static properties =====
 
    [WcfFact]
    public static void TransactionProtocol_Default_Returns_OleTransactions()
    {
        Assert.Same(TransactionProtocol.OleTransactions, TransactionProtocol.Default);
    }
 
    [WcfFact]
    public static void TransactionProtocol_OleTransactions_Is_Not_Null()
    {
        Assert.NotNull(TransactionProtocol.OleTransactions);
    }
 
    [WcfFact]
    public static void TransactionProtocol_WSAtomicTransactionOctober2004_Is_Not_Null()
    {
        Assert.NotNull(TransactionProtocol.WSAtomicTransactionOctober2004);
    }
 
    [WcfFact]
    public static void TransactionProtocol_WSAtomicTransaction11_Is_Not_Null()
    {
        Assert.NotNull(TransactionProtocol.WSAtomicTransaction11);
    }
 
    [WcfFact]
    public static void TransactionProtocol_Singletons_Are_Unique()
    {
        Assert.NotSame(TransactionProtocol.OleTransactions, TransactionProtocol.WSAtomicTransactionOctober2004);
        Assert.NotSame(TransactionProtocol.OleTransactions, TransactionProtocol.WSAtomicTransaction11);
        Assert.NotSame(TransactionProtocol.WSAtomicTransactionOctober2004, TransactionProtocol.WSAtomicTransaction11);
    }
 
    // ===== TransactionFlowOption enum tests =====
 
    [WcfFact]
    public static void TransactionFlowOption_Values_Are_Correct()
    {
        Assert.Equal(0, (int)TransactionFlowOption.NotAllowed);
        Assert.Equal(1, (int)TransactionFlowOption.Allowed);
        Assert.Equal(2, (int)TransactionFlowOption.Mandatory);
    }
 
    [WcfFact]
    public static void TransactionFlowOption_All_Values_Are_Distinct()
    {
        Assert.NotEqual(TransactionFlowOption.NotAllowed, TransactionFlowOption.Allowed);
        Assert.NotEqual(TransactionFlowOption.NotAllowed, TransactionFlowOption.Mandatory);
        Assert.NotEqual(TransactionFlowOption.Allowed, TransactionFlowOption.Mandatory);
    }
 
    // ===== TransactionFlowAttribute tests =====
 
    [WcfFact]
    public static void TransactionFlowAttribute_Ctor_Sets_Transactions_Property()
    {
        TransactionFlowAttribute attr = new TransactionFlowAttribute(TransactionFlowOption.Allowed);
        Assert.Equal(TransactionFlowOption.Allowed, attr.Transactions);
    }
 
    [WcfFact]
    public static void TransactionFlowAttribute_Ctor_NotAllowed()
    {
        TransactionFlowAttribute attr = new TransactionFlowAttribute(TransactionFlowOption.NotAllowed);
        Assert.Equal(TransactionFlowOption.NotAllowed, attr.Transactions);
    }
 
    [WcfFact]
    public static void TransactionFlowAttribute_Ctor_Mandatory()
    {
        TransactionFlowAttribute attr = new TransactionFlowAttribute(TransactionFlowOption.Mandatory);
        Assert.Equal(TransactionFlowOption.Mandatory, attr.Transactions);
    }
 
    [WcfFact]
    public static void TransactionFlowAttribute_Invalid_Option_Throws()
    {
        Assert.Throws<ArgumentException>(() => new TransactionFlowAttribute((TransactionFlowOption)99));
    }
 
    [WcfTheory]
    [InlineData(TransactionFlowOption.NotAllowed)]
    [InlineData(TransactionFlowOption.Allowed)]
    [InlineData(TransactionFlowOption.Mandatory)]
    public static void TransactionFlowAttribute_All_Valid_Options_Accepted(TransactionFlowOption option)
    {
        TransactionFlowAttribute attr = new TransactionFlowAttribute(option);
        Assert.Equal(option, attr.Transactions);
    }
 
    [WcfTheory]
    [InlineData(-1)]
    [InlineData(3)]
    [InlineData(99)]
    [InlineData(int.MaxValue)]
    public static void TransactionFlowAttribute_Invalid_Options_Throw(int invalidValue)
    {
        Assert.Throws<ArgumentException>(() => new TransactionFlowAttribute((TransactionFlowOption)invalidValue));
    }
 
    [WcfFact]
    public static void TransactionFlowAttribute_Is_AttributeUsage_Method()
    {
        var usageAttr = (AttributeUsageAttribute)Attribute.GetCustomAttribute(
            typeof(TransactionFlowAttribute), typeof(AttributeUsageAttribute));
        Assert.NotNull(usageAttr);
        Assert.Equal(AttributeTargets.Method, usageAttr.ValidOn);
    }
 
    [WcfFact]
    public static void TransactionFlowAttribute_Implements_IOperationBehavior()
    {
        TransactionFlowAttribute attr = new TransactionFlowAttribute(TransactionFlowOption.Allowed);
        Assert.IsAssignableFrom<System.ServiceModel.Description.IOperationBehavior>(attr);
    }
 
    // ===== TransactionFlowAttribute with each option preserves across read =====
 
    [WcfFact]
    public static void TransactionFlowAttribute_NotAllowed_Transactions_Property_Is_Stable()
    {
        TransactionFlowAttribute attr = new TransactionFlowAttribute(TransactionFlowOption.NotAllowed);
        // Read the property multiple times to ensure stability
        Assert.Equal(TransactionFlowOption.NotAllowed, attr.Transactions);
        Assert.Equal(TransactionFlowOption.NotAllowed, attr.Transactions);
    }
 
    [WcfFact]
    public static void TransactionFlowAttribute_Mandatory_Transactions_Property_Is_Stable()
    {
        TransactionFlowAttribute attr = new TransactionFlowAttribute(TransactionFlowOption.Mandatory);
        Assert.Equal(TransactionFlowOption.Mandatory, attr.Transactions);
        Assert.Equal(TransactionFlowOption.Mandatory, attr.Transactions);
    }
 
    // ===== TransactionMessageProperty tests =====
 
    [WcfFact]
    public static void TransactionMessageProperty_Set_With_Valid_Message()
    {
        // Verify Set method works with a valid message (without a real transaction, just test it doesn't throw on the property add)
        Message message = Message.CreateMessage(MessageVersion.Default, "test");
        // We can't easily create a Transaction in a unit test on Windows without DTC,
        // so we just verify that Set with null transaction adds the property.
        TransactionMessageProperty.Set(null, message);
        // Accessing Transaction property should return null since we set null
    }
}
 
// ===== Platform-specific tests (PlatformNotSupportedException on non-Windows) =====
// These tests validate that on non-Windows platforms, enabling transaction flow
// features that require DTC/OleTx will throw PlatformNotSupportedException.
// They won't execute on Windows (where transactions are supported), but they must exist.
 
public static class TransactionFlowPlatformTests
{
    // This condition method returns true only on non-Windows platforms.
    public static bool Is_Not_Windows()
    {
        return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
    }
 
    [WcfFact]
    [Condition(nameof(Is_Not_Windows))]
    public static void TransactionFormatter_OleTx_Throws_PlatformNotSupported_On_NonWindows()
    {
        var formatter = GetFormatter("OleTxFormatter");
        Assert.Throws<PlatformNotSupportedException>(() =>
            InvokeWriteTransaction(formatter, null, null));
    }
 
    [WcfFact]
    [Condition(nameof(Is_Not_Windows))]
    public static void TransactionFormatter_WsatFormatter10_Throws_PlatformNotSupported_On_NonWindows()
    {
        var formatter = GetFormatter("WsatFormatter10");
        Assert.Throws<PlatformNotSupportedException>(() =>
            InvokeWriteTransaction(formatter, null, null));
    }
 
    [WcfFact]
    [Condition(nameof(Is_Not_Windows))]
    public static void TransactionFormatter_WsatFormatter11_Throws_PlatformNotSupported_On_NonWindows()
    {
        var formatter = GetFormatter("WsatFormatter11");
        Assert.Throws<PlatformNotSupportedException>(() =>
            InvokeWriteTransaction(formatter, null, null));
    }
 
    [WcfFact]
    [Condition(nameof(Is_Not_Windows))]
    public static void TransactionFormatter_OleTx_ReadTransaction_Throws_PlatformNotSupported_On_NonWindows()
    {
        var formatter = GetFormatter("OleTxFormatter");
        Assert.Throws<PlatformNotSupportedException>(() =>
            InvokeReadTransaction(formatter, null));
    }
 
    [WcfFact]
    [Condition(nameof(Is_Not_Windows))]
    public static void TransactionFormatter_OleTx_EmptyHeader_Throws_PlatformNotSupported_On_NonWindows()
    {
        var formatter = GetFormatter("OleTxFormatter");
        Assert.Throws<PlatformNotSupportedException>(() =>
            GetEmptyTransactionHeader(formatter));
    }
 
    private static readonly Type s_formatterType = typeof(TransactionFlowBindingElement).Assembly
        .GetType("System.ServiceModel.Transactions.TransactionFormatter");
 
    private static object GetFormatter(string propertyName)
    {
        var prop = s_formatterType.GetProperty(propertyName,
            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
        return prop.GetValue(null);
    }
 
    private static void InvokeWriteTransaction(object formatter, object transaction, object message)
    {
        try
        {
            s_formatterType.GetMethod("WriteTransaction").Invoke(formatter, new[] { transaction, message });
        }
        catch (System.Reflection.TargetInvocationException ex) when (ex.InnerException != null)
        {
            System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
        }
    }
 
    private static void InvokeReadTransaction(object formatter, object message)
    {
        try
        {
            s_formatterType.GetMethod("ReadTransaction").Invoke(formatter, new[] { message });
        }
        catch (System.Reflection.TargetInvocationException ex) when (ex.InnerException != null)
        {
            System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
        }
    }
 
    private static object GetEmptyTransactionHeader(object formatter)
    {
        try
        {
            return s_formatterType.GetProperty("EmptyTransactionHeader").GetValue(formatter);
        }
        catch (System.Reflection.TargetInvocationException ex) when (ex.InnerException != null)
        {
            System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
            return null; // unreachable
        }
    }
}