shoc-backend/SeaHavenIndustries.Tests/WorkOrderBoardMutationRulesTests.cs
Alexandre Brandizzi ee24dde218 fix(workorders): let Cancel through the Past Due status lock
Past Due blocks lifecycle status updates until Schedule On moves (SH-262),
but it also rejected Cancel WO, which the detail panel offers on Past Due
work orders. Dispatchers had to fake a reschedule to cancel. Cancel is its
own action (SH-113), so only Canceled is exempt; every other status change
stays blocked.
2026-10-01 20:02:25 -03:00

169 lines
6.4 KiB
C#

using Data.SeaHavenIndustries.Enums;
using SeaHaven.Services.Helpers;
namespace SeaHavenIndustries.Tests;
public class WorkOrderBoardMutationRulesTests
{
[Theory]
[InlineData(LifecycleStatus.Canceled, true)]
[InlineData(LifecycleStatus.Completed, true)]
[InlineData(LifecycleStatus.Scheduled, false)]
[InlineData(LifecycleStatus.Incomplete, false)]
public void IsReadOnly_ReflectsTerminalStatuses(LifecycleStatus status, bool expected)
{
Assert.Equal(expected, WorkOrderBoardMutationRules.IsReadOnly(status));
}
[Fact]
public void ShouldBlockStatusChangeWhenPastDue_BlocksOnlyLifecycleStatus()
{
Assert.True(WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue(
WorkOrderBoardFieldNames.LifecycleStatus, isPastDue: true, LifecycleStatus.InProgress));
Assert.False(WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue(
WorkOrderBoardFieldNames.ScheduledDate, isPastDue: true, null));
Assert.False(WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue(
WorkOrderBoardFieldNames.DueDate, isPastDue: true, null));
}
[Theory]
[InlineData(LifecycleStatus.Canceled, false)]
[InlineData(LifecycleStatus.Completed, true)]
[InlineData(LifecycleStatus.EnRoute, true)]
public void ShouldBlockStatusChangeWhenPastDue_LetsOnlyCancelThrough(LifecycleStatus requested, bool expected)
{
Assert.Equal(expected, WorkOrderBoardMutationRules.ShouldBlockStatusChangeWhenPastDue(
WorkOrderBoardFieldNames.LifecycleStatus, isPastDue: true, requested));
}
[Fact]
public void ShouldAutoSchedule_WhenIncompleteWithDate_DoesNotRequireAssignee()
{
Assert.True(WorkOrderBoardMutationRules.ShouldAutoSchedule(
LifecycleStatus.Incomplete,
new DateTime(2026, 6, 25),
assignTo: null));
}
[Fact]
public void ShouldAutoSchedule_WhenPendingWithConcreteDate()
{
Assert.True(WorkOrderBoardMutationRules.ShouldAutoSchedule(
LifecycleStatus.Pending,
new DateTime(2026, 6, 25),
assignTo: null));
}
[Fact]
public void ShouldAutoSchedule_FalseWhenAlreadyScheduled()
{
Assert.False(WorkOrderBoardMutationRules.ShouldAutoSchedule(
LifecycleStatus.Scheduled,
new DateTime(2026, 6, 25),
assignTo: null));
}
[Fact]
public void ShouldAutoSchedule_FalseWhenScheduleWeekOnly()
{
Assert.False(WorkOrderBoardMutationRules.ShouldAutoSchedule(
LifecycleStatus.Incomplete,
new DateTime(2026, 6, 25),
assignTo: null,
scheduleWeekOnly: true));
}
[Fact]
public void ShouldAutoSchedule_FalseWhenInProgress()
{
Assert.False(WorkOrderBoardMutationRules.ShouldAutoSchedule(
LifecycleStatus.InProgress,
new DateTime(2026, 6, 25),
assignTo: null));
}
[Fact]
public void ShouldDemoteFromScheduled_WhenDateCleared()
{
Assert.True(WorkOrderBoardMutationRules.ShouldDemoteFromScheduled(
LifecycleStatus.Scheduled,
scheduledDate: null));
}
[Fact]
public void ShouldDemoteFromScheduled_WhenWeekOnly()
{
Assert.True(WorkOrderBoardMutationRules.ShouldDemoteFromScheduled(
LifecycleStatus.Scheduled,
new DateTime(2026, 6, 25),
scheduleWeekOnly: true));
}
[Fact]
public void ShouldDemoteFromScheduled_FalseWhenConcreteDateRemains()
{
Assert.False(WorkOrderBoardMutationRules.ShouldDemoteFromScheduled(
LifecycleStatus.Scheduled,
new DateTime(2026, 6, 25)));
}
[Fact]
public void ShouldDemoteFromScheduled_FalseWhenInProgress()
{
Assert.False(WorkOrderBoardMutationRules.ShouldDemoteFromScheduled(
LifecycleStatus.InProgress,
scheduledDate: null));
}
[Fact]
public void CanPatchBoardField_AllowsOnlyFlagColorWhenCompleted()
{
Assert.True(WorkOrderBoardMutationRules.CanPatchBoardField(
LifecycleStatus.Completed, WorkOrderBoardFieldNames.FlagColor));
Assert.False(WorkOrderBoardMutationRules.CanPatchBoardField(
LifecycleStatus.Completed, WorkOrderBoardFieldNames.LifecycleStatus));
Assert.False(WorkOrderBoardMutationRules.CanPatchBoardField(
LifecycleStatus.Completed, WorkOrderBoardFieldNames.SiteCode));
Assert.False(WorkOrderBoardMutationRules.CanPatchBoardField(
LifecycleStatus.Canceled, WorkOrderBoardFieldNames.FlagColor));
Assert.True(WorkOrderBoardMutationRules.CanPatchBoardField(
LifecycleStatus.Scheduled, WorkOrderBoardFieldNames.AssignTo));
}
[Fact]
public void CanMutateComments_FalseOnlyWhenCanceled()
{
Assert.True(WorkOrderBoardMutationRules.CanMutateComments(LifecycleStatus.Completed));
Assert.True(WorkOrderBoardMutationRules.CanMutateComments(LifecycleStatus.Scheduled));
Assert.False(WorkOrderBoardMutationRules.CanMutateComments(LifecycleStatus.Canceled));
}
[Fact]
public void CanMutateExtraMedia_AllowsExtraOnCompleted()
{
Assert.True(WorkOrderBoardMutationRules.CanMutateExtraMedia(
LifecycleStatus.Completed, WorkOrderMediaCategory.Extra));
Assert.True(WorkOrderBoardMutationRules.CanMutateExtraMedia(LifecycleStatus.Completed, null));
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
LifecycleStatus.Completed, WorkOrderMediaCategory.Before));
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
LifecycleStatus.Completed, WorkOrderMediaCategory.After));
Assert.True(WorkOrderBoardMutationRules.CanMutateExtraMedia(
LifecycleStatus.Completed, WorkOrderMediaCategory.Aveta));
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
LifecycleStatus.Canceled, WorkOrderMediaCategory.Extra));
Assert.False(WorkOrderBoardMutationRules.CanMutateExtraMedia(
LifecycleStatus.Canceled, WorkOrderMediaCategory.Aveta));
}
[Fact]
public void IsReschedule_WhenPreviousDateExistsAndChanges()
{
Assert.True(WorkOrderBoardMutationRules.IsReschedule(
new DateTime(2026, 6, 20),
new DateTime(2026, 6, 25)));
Assert.False(WorkOrderBoardMutationRules.IsReschedule(
null,
new DateTime(2026, 6, 25)));
}
}