420 lines
12 KiB
C#
420 lines
12 KiB
C#
using FluentAssertions;
|
|
using Workflow.Domain.Enums;
|
|
using Workflow.Domain.Exceptions;
|
|
using Workflow.Domain.StateMachine;
|
|
using Xunit;
|
|
|
|
namespace Workflow.Tests.StateMachine;
|
|
|
|
/// <summary>
|
|
/// InstanceStatusMachine TDD 红灯阶段测试
|
|
/// 测试工作流实例状态机的所有合法转换与非法转换
|
|
///
|
|
/// 实例状态: Running, Suspended, Completed, Terminated
|
|
/// 操作: Complete, Suspend, Resume, Terminate, Withdraw
|
|
/// </summary>
|
|
public class InstanceStatusMachineTests
|
|
{
|
|
private readonly InstanceStateMachine _machine = new();
|
|
|
|
#region Running → Completed
|
|
|
|
[Fact]
|
|
public void Transition_Running_To_Completed_When_No_Active_Tokens()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Complete;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
HasActiveTokens = false,
|
|
};
|
|
|
|
// Act
|
|
var newState = _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
newState.Should().Be(InstanceStatus.Completed);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Running_Complete_With_Active_Tokens_Should_Throw()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Complete;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
HasActiveTokens = true,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>()
|
|
.WithMessage("*active token*");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Running → Suspended
|
|
|
|
[Fact]
|
|
public void Transition_Running_To_Suspended_By_Admin()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Suspend;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
IsInitiator = false,
|
|
};
|
|
|
|
// Act
|
|
var newState = _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
newState.Should().Be(InstanceStatus.Suspended);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Running_To_Suspended_By_Initiator()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Suspend;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = false,
|
|
IsInitiator = true,
|
|
};
|
|
|
|
// Act
|
|
var newState = _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
newState.Should().Be(InstanceStatus.Suspended);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Running_Suspend_By_Unauthorized_User_Should_Throw()
|
|
{
|
|
// Arrange - 既不是 Admin 也不是 Initiator
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Suspend;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = false,
|
|
IsInitiator = false,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>()
|
|
.WithMessage("*admin*initiator*");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Suspended → Running
|
|
|
|
[Fact]
|
|
public void Transition_Suspended_To_Running_By_Admin()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Suspended;
|
|
var operation = InstanceOperation.Resume;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
IsInitiator = false,
|
|
};
|
|
|
|
// Act
|
|
var newState = _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
newState.Should().Be(InstanceStatus.Running);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Suspended_To_Running_By_Initiator()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Suspended;
|
|
var operation = InstanceOperation.Resume;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = false,
|
|
IsInitiator = true,
|
|
};
|
|
|
|
// Act
|
|
var newState = _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
newState.Should().Be(InstanceStatus.Running);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Suspended_Resume_By_Unauthorized_User_Should_Throw()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Suspended;
|
|
var operation = InstanceOperation.Resume;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = false,
|
|
IsInitiator = false,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>()
|
|
.WithMessage("*admin*initiator*");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Running → Terminated
|
|
|
|
[Fact]
|
|
public void Transition_Running_To_Terminated_By_Admin()
|
|
{
|
|
// Arrange
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Terminate;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
};
|
|
|
|
// Act
|
|
var newState = _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
newState.Should().Be(InstanceStatus.Terminated);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Running_Terminate_By_Non_Admin_Should_Throw()
|
|
{
|
|
// Arrange - Terminate 操作要求 Admin 权限
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Terminate;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = false,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>()
|
|
.WithMessage("*admin*");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Running → Terminated (Withdraw)
|
|
|
|
[Fact]
|
|
public void Transition_Running_Withdraw_By_Initiator_With_No_Processed_Nodes()
|
|
{
|
|
// Arrange - 发起人撤回,且没有任何节点被处理过
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Withdraw;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsInitiator = true,
|
|
HasProcessedNodes = false,
|
|
};
|
|
|
|
// Act
|
|
var newState = _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
newState.Should().Be(InstanceStatus.Terminated);
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Running_Withdraw_By_Non_Initiator_Should_Throw()
|
|
{
|
|
// Arrange - 非发起人尝试撤回
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Withdraw;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsInitiator = false,
|
|
HasProcessedNodes = false,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>()
|
|
.WithMessage("*initiator*");
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Running_Withdraw_With_Processed_Nodes_Should_Throw()
|
|
{
|
|
// Arrange - 发起人撤回,但已有节点被处理过
|
|
var currentState = InstanceStatus.Running;
|
|
var operation = InstanceOperation.Withdraw;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsInitiator = true,
|
|
HasProcessedNodes = true,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>()
|
|
.WithMessage("*processed*");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Illegal Transitions
|
|
|
|
[Fact]
|
|
public void Transition_Completed_To_Running_Should_Throw()
|
|
{
|
|
// Arrange - 已完成的实例不能回到运行中
|
|
var currentState = InstanceStatus.Completed;
|
|
var operation = InstanceOperation.Resume;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Terminated_To_Suspended_Should_Throw()
|
|
{
|
|
// Arrange - 已终止的实例不能挂起
|
|
var currentState = InstanceStatus.Terminated;
|
|
var operation = InstanceOperation.Suspend;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Terminated_To_Running_Should_Throw()
|
|
{
|
|
// Arrange - 已终止的实例不能恢复运行
|
|
var currentState = InstanceStatus.Terminated;
|
|
var operation = InstanceOperation.Resume;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Completed_To_Terminated_Should_Throw()
|
|
{
|
|
// Arrange - 已完成的实例不能终止
|
|
var currentState = InstanceStatus.Completed;
|
|
var operation = InstanceOperation.Terminate;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Suspended_To_Completed_Should_Throw()
|
|
{
|
|
// Arrange - 挂起状态的实例不能直接完成
|
|
var currentState = InstanceStatus.Suspended;
|
|
var operation = InstanceOperation.Complete;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
HasActiveTokens = false,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Suspended_To_Terminated_Should_Throw()
|
|
{
|
|
// Arrange - 挂起状态的实例不能直接终止,需先恢复
|
|
var currentState = InstanceStatus.Suspended;
|
|
var operation = InstanceOperation.Terminate;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsAdmin = true,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Transition_Suspended_Withdraw_Should_Throw()
|
|
{
|
|
// Arrange - 挂起状态不允许撤回操作
|
|
var currentState = InstanceStatus.Suspended;
|
|
var operation = InstanceOperation.Withdraw;
|
|
var context = new InstanceTransitionContext
|
|
{
|
|
IsInitiator = true,
|
|
HasProcessedNodes = false,
|
|
};
|
|
|
|
// Act
|
|
var act = () => _machine.Transition(currentState, operation, context);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidStateTransitionException>();
|
|
}
|
|
|
|
#endregion
|
|
}
|