wip
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Modules.Approvals.Data;
|
||||
using Socialize.Api.Modules.Approvals.Services;
|
||||
|
||||
namespace Socialize.Tests.Approvals;
|
||||
|
||||
public class ApprovalWorkflowRulesTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ApprovalModes.Optional, true)]
|
||||
[InlineData(ApprovalModes.Required, true)]
|
||||
[InlineData(ApprovalModes.None, false)]
|
||||
[InlineData(ApprovalModes.MultiLevel, false)]
|
||||
public void CanCreateSingleStepApprovalRequest_matches_basic_modes(string approvalMode, bool expected)
|
||||
{
|
||||
bool actual = ApprovalWorkflowRules.CanCreateSingleStepApprovalRequest(approvalMode);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ApprovalModes.Required, true)]
|
||||
[InlineData(ApprovalModes.MultiLevel, true)]
|
||||
[InlineData(ApprovalModes.Optional, false)]
|
||||
[InlineData(ApprovalModes.None, false)]
|
||||
public void BlocksManualApprovedOrScheduledStatus_matches_blocking_modes(string approvalMode, bool expected)
|
||||
{
|
||||
bool actual = ApprovalWorkflowRules.BlocksManualApprovedOrScheduledStatus(approvalMode);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Approved", true)]
|
||||
[InlineData("Scheduled", true)]
|
||||
[InlineData("In approval", false)]
|
||||
[InlineData("Published", false)]
|
||||
public void IsApprovalCompletionStatus_only_matches_approval_gate_destinations(string status, bool expected)
|
||||
{
|
||||
bool actual = ApprovalWorkflowRules.IsApprovalCompletionStatus(status);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFinalApprovalStatus_schedules_when_option_enabled_and_publish_date_exists()
|
||||
{
|
||||
string status = ApprovalWorkflowRules.GetFinalApprovalStatus(
|
||||
schedulePostsAutomaticallyOnApproval: true,
|
||||
plannedPublishDate: DateTimeOffset.UtcNow.AddDays(1));
|
||||
|
||||
Assert.Equal("Scheduled", status);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void GetFinalApprovalStatus_approves_when_auto_schedule_disabled_or_date_missing(bool scheduleAutomatically)
|
||||
{
|
||||
string status = ApprovalWorkflowRules.GetFinalApprovalStatus(
|
||||
scheduleAutomatically,
|
||||
plannedPublishDate: null);
|
||||
|
||||
Assert.Equal("Approved", status);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, 1, true)]
|
||||
[InlineData(1, 2, false)]
|
||||
[InlineData(2, 2, true)]
|
||||
[InlineData(1, 0, true)]
|
||||
public void HasRequiredStepApprovals_enforces_configured_approver_count(
|
||||
int approvedDecisionCount,
|
||||
int requiredApproverCount,
|
||||
bool expected)
|
||||
{
|
||||
bool actual = ApprovalWorkflowRules.HasRequiredStepApprovals(
|
||||
approvedDecisionCount,
|
||||
requiredApproverCount);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApproveWorkflowStep_allows_admin_for_any_step_target()
|
||||
{
|
||||
bool actual = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: true,
|
||||
hasWorkspaceAccess: false,
|
||||
userRoles: [],
|
||||
userId: Guid.NewGuid(),
|
||||
targetType: ApprovalStepTargetTypes.Member,
|
||||
targetValue: Guid.NewGuid().ToString());
|
||||
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApproveWorkflowStep_requires_role_target_match()
|
||||
{
|
||||
bool actual = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: false,
|
||||
hasWorkspaceAccess: true,
|
||||
userRoles: ["manager"],
|
||||
userId: Guid.NewGuid(),
|
||||
targetType: ApprovalStepTargetTypes.Role,
|
||||
targetValue: "manager");
|
||||
|
||||
Assert.True(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApproveWorkflowStep_rejects_later_step_actor_without_target_match()
|
||||
{
|
||||
bool actual = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: false,
|
||||
hasWorkspaceAccess: true,
|
||||
userRoles: ["provider"],
|
||||
userId: Guid.NewGuid(),
|
||||
targetType: ApprovalStepTargetTypes.Role,
|
||||
targetValue: "client");
|
||||
|
||||
Assert.False(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApproveWorkflowStep_requires_member_target_identity_match()
|
||||
{
|
||||
Guid assignedMemberId = Guid.NewGuid();
|
||||
Guid secondAssignedMemberId = Guid.NewGuid();
|
||||
|
||||
bool matchingMember = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: false,
|
||||
hasWorkspaceAccess: true,
|
||||
userRoles: ["workspace-member"],
|
||||
userId: assignedMemberId,
|
||||
targetType: ApprovalStepTargetTypes.Member,
|
||||
targetValue: $"{assignedMemberId},{secondAssignedMemberId}");
|
||||
|
||||
bool otherMember = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: false,
|
||||
hasWorkspaceAccess: true,
|
||||
userRoles: ["workspace-member"],
|
||||
userId: Guid.NewGuid(),
|
||||
targetType: ApprovalStepTargetTypes.Member,
|
||||
targetValue: $"{assignedMemberId},{secondAssignedMemberId}");
|
||||
|
||||
Assert.True(matchingMember);
|
||||
Assert.False(otherMember);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseMemberTargetIds_reads_distinct_comma_separated_member_ids()
|
||||
{
|
||||
Guid firstMemberId = Guid.NewGuid();
|
||||
Guid secondMemberId = Guid.NewGuid();
|
||||
|
||||
IReadOnlyCollection<Guid> memberIds = ApprovalWorkflowRules.ParseMemberTargetIds(
|
||||
$" {firstMemberId},not-a-guid,{secondMemberId},{firstMemberId} ");
|
||||
|
||||
Assert.Equal([firstMemberId, secondMemberId], memberIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FormatMemberTargetValue_stores_member_ids_stably()
|
||||
{
|
||||
Guid firstMemberId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
|
||||
Guid secondMemberId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");
|
||||
|
||||
string targetValue = ApprovalWorkflowRules.FormatMemberTargetValue(
|
||||
[
|
||||
secondMemberId,
|
||||
firstMemberId,
|
||||
secondMemberId,
|
||||
]);
|
||||
|
||||
Assert.Equal($"{firstMemberId},{secondMemberId}", targetValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanApproveWorkflowStep_matches_membership_targets()
|
||||
{
|
||||
bool clientMatchesClient = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: false,
|
||||
hasWorkspaceAccess: true,
|
||||
userRoles: ["client"],
|
||||
userId: Guid.NewGuid(),
|
||||
targetType: ApprovalStepTargetTypes.Membership,
|
||||
targetValue: ApprovalMembershipTargets.Client);
|
||||
|
||||
bool providerMatchesTeam = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: false,
|
||||
hasWorkspaceAccess: true,
|
||||
userRoles: ["provider"],
|
||||
userId: Guid.NewGuid(),
|
||||
targetType: ApprovalStepTargetTypes.Membership,
|
||||
targetValue: ApprovalMembershipTargets.Team);
|
||||
|
||||
bool clientDoesNotMatchTeam = ApprovalWorkflowRules.CanApproveWorkflowStep(
|
||||
isAdministrator: false,
|
||||
hasWorkspaceAccess: true,
|
||||
userRoles: ["client"],
|
||||
userId: Guid.NewGuid(),
|
||||
targetType: ApprovalStepTargetTypes.Membership,
|
||||
targetValue: ApprovalMembershipTargets.Team);
|
||||
|
||||
Assert.True(clientMatchesClient);
|
||||
Assert.True(providerMatchesTeam);
|
||||
Assert.False(clientDoesNotMatchTeam);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ApprovalStepTargetTypes.Role)]
|
||||
[InlineData(ApprovalStepTargetTypes.Membership)]
|
||||
[InlineData(ApprovalStepTargetTypes.Member)]
|
||||
public void IsValidTargetType_accepts_supported_target_types(string targetType)
|
||||
{
|
||||
bool valid = ApprovalStepConfigurationRules.IsValidTargetType(targetType);
|
||||
|
||||
Assert.True(valid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("Group")]
|
||||
[InlineData("role")]
|
||||
public void IsValidTargetType_rejects_unsupported_target_types(string targetType)
|
||||
{
|
||||
bool valid = ApprovalStepConfigurationRules.IsValidTargetType(targetType);
|
||||
|
||||
Assert.False(valid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("administrator")]
|
||||
[InlineData("manager")]
|
||||
[InlineData("workspace-member")]
|
||||
[InlineData("client")]
|
||||
[InlineData("provider")]
|
||||
public void IsValidRoleTarget_accepts_known_workspace_roles(string role)
|
||||
{
|
||||
bool valid = ApprovalStepConfigurationRules.IsValidRoleTarget(role);
|
||||
|
||||
Assert.True(valid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("developer")]
|
||||
[InlineData("owner")]
|
||||
public void IsValidRoleTarget_rejects_non_workspace_approval_roles(string role)
|
||||
{
|
||||
bool valid = ApprovalStepConfigurationRules.IsValidRoleTarget(role);
|
||||
|
||||
Assert.False(valid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ApprovalMembershipTargets.Team)]
|
||||
[InlineData(ApprovalMembershipTargets.Client)]
|
||||
public void IsValidMembershipTarget_accepts_supported_memberships(string membership)
|
||||
{
|
||||
bool valid = ApprovalStepConfigurationRules.IsValidMembershipTarget(membership);
|
||||
|
||||
Assert.True(valid);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("Provider")]
|
||||
[InlineData("External")]
|
||||
public void IsValidMembershipTarget_rejects_unsupported_memberships(string membership)
|
||||
{
|
||||
bool valid = ApprovalStepConfigurationRules.IsValidMembershipTarget(membership);
|
||||
|
||||
Assert.False(valid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkspaceApprovalStepConfiguration_model_persists_workspace_ordering()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseNpgsql("Host=localhost;Database=socialize_model_test")
|
||||
.Options;
|
||||
using var dbContext = new AppDbContext(options);
|
||||
|
||||
var entity = dbContext.Model.FindEntityType(typeof(WorkspaceApprovalStepConfiguration));
|
||||
|
||||
Assert.NotNull(entity);
|
||||
Assert.Equal("WorkspaceApprovalStepConfigurations", entity.GetTableName());
|
||||
Assert.Equal(128, entity.FindProperty(nameof(WorkspaceApprovalStepConfiguration.Name))?.GetMaxLength());
|
||||
Assert.Equal(32, entity.FindProperty(nameof(WorkspaceApprovalStepConfiguration.TargetType))?.GetMaxLength());
|
||||
Assert.Equal(128, entity.FindProperty(nameof(WorkspaceApprovalStepConfiguration.TargetValue))?.GetMaxLength());
|
||||
Assert.Contains(
|
||||
entity.GetIndexes(),
|
||||
index => index.IsUnique &&
|
||||
index.Properties.Select(property => property.Name).SequenceEqual(
|
||||
[
|
||||
nameof(WorkspaceApprovalStepConfiguration.WorkspaceId),
|
||||
nameof(WorkspaceApprovalStepConfiguration.SortOrder),
|
||||
]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApprovalWorkflowInstance_model_allows_only_one_pending_workflow_per_content_item()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseNpgsql("Host=localhost;Database=socialize_model_test")
|
||||
.Options;
|
||||
using var dbContext = new AppDbContext(options);
|
||||
|
||||
var entity = dbContext.Model.FindEntityType(typeof(ApprovalWorkflowInstance));
|
||||
|
||||
Assert.NotNull(entity);
|
||||
Assert.Equal("ApprovalWorkflowInstances", entity.GetTableName());
|
||||
Assert.Equal(64, entity.FindProperty(nameof(ApprovalWorkflowInstance.State))?.GetMaxLength());
|
||||
Assert.Equal(64, entity.FindProperty(nameof(ApprovalWorkflowInstance.ApprovalMode))?.GetMaxLength());
|
||||
Assert.Contains(
|
||||
entity.GetIndexes(),
|
||||
index => index.IsUnique &&
|
||||
index.GetFilter() == "\"State\" = 'Pending'" &&
|
||||
index.Properties.Select(property => property.Name).SequenceEqual(
|
||||
[
|
||||
nameof(ApprovalWorkflowInstance.ContentItemId),
|
||||
nameof(ApprovalWorkflowInstance.State),
|
||||
]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApprovalRequest_model_persists_runtime_step_metadata()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseNpgsql("Host=localhost;Database=socialize_model_test")
|
||||
.Options;
|
||||
using var dbContext = new AppDbContext(options);
|
||||
|
||||
var entity = dbContext.Model.FindEntityType(typeof(ApprovalRequest));
|
||||
|
||||
Assert.NotNull(entity);
|
||||
Assert.Equal(32, entity.FindProperty(nameof(ApprovalRequest.WorkflowStepTargetType))?.GetMaxLength());
|
||||
Assert.Equal(128, entity.FindProperty(nameof(ApprovalRequest.WorkflowStepTargetValue))?.GetMaxLength());
|
||||
Assert.Contains(
|
||||
entity.GetIndexes(),
|
||||
index => index.Properties.Select(property => property.Name).SequenceEqual(
|
||||
[
|
||||
nameof(ApprovalRequest.WorkflowInstanceId),
|
||||
]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user