Add unit tests for ExceptionEvaluator, ExceptionEvent, ExceptionHistory, and ExceptionObject models
- Implemented comprehensive unit tests for the ExceptionEvaluator service, covering various scenarios including matching exceptions, environment checks, and evidence references. - Created tests for the ExceptionEvent model to validate event creation methods and ensure correct event properties. - Developed tests for the ExceptionHistory model to verify event count, order, and timestamps. - Added tests for the ExceptionObject domain model to ensure validity checks and property preservation for various fields.
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Policy.Exceptions.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Exceptions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for ExceptionObject domain model.
|
||||
/// </summary>
|
||||
public sealed class ExceptionObjectTests
|
||||
{
|
||||
[Fact]
|
||||
public void ExceptionObject_WithValidScope_ShouldBeValid()
|
||||
{
|
||||
// Arrange & Act
|
||||
var scope = new ExceptionScope
|
||||
{
|
||||
VulnerabilityId = "CVE-2024-12345"
|
||||
};
|
||||
|
||||
// Assert
|
||||
scope.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionScope_WithNoConstraints_ShouldBeInvalid()
|
||||
{
|
||||
// Arrange & Act
|
||||
var scope = new ExceptionScope();
|
||||
|
||||
// Assert
|
||||
scope.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionScope_WithArtifactDigest_ShouldBeValid()
|
||||
{
|
||||
// Arrange & Act
|
||||
var scope = new ExceptionScope
|
||||
{
|
||||
ArtifactDigest = "sha256:abc123def456"
|
||||
};
|
||||
|
||||
// Assert
|
||||
scope.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionScope_WithPurlPattern_ShouldBeValid()
|
||||
{
|
||||
// Arrange & Act
|
||||
var scope = new ExceptionScope
|
||||
{
|
||||
PurlPattern = "pkg:npm/lodash@*"
|
||||
};
|
||||
|
||||
// Assert
|
||||
scope.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionScope_WithPolicyRuleId_ShouldBeValid()
|
||||
{
|
||||
// Arrange & Act
|
||||
var scope = new ExceptionScope
|
||||
{
|
||||
PolicyRuleId = "no-root-containers"
|
||||
};
|
||||
|
||||
// Assert
|
||||
scope.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_IsEffective_WhenActiveAndNotExpired_ShouldBeTrue()
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(
|
||||
status: ExceptionStatus.Active,
|
||||
expiresAt: DateTimeOffset.UtcNow.AddDays(30));
|
||||
|
||||
// Act & Assert
|
||||
exception.IsEffective.Should().BeTrue();
|
||||
exception.HasExpired.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_IsEffective_WhenActiveButExpired_ShouldBeFalse()
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(
|
||||
status: ExceptionStatus.Active,
|
||||
expiresAt: DateTimeOffset.UtcNow.AddDays(-1));
|
||||
|
||||
// Act & Assert
|
||||
exception.IsEffective.Should().BeFalse();
|
||||
exception.HasExpired.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_IsEffective_WhenProposed_ShouldBeFalse()
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(
|
||||
status: ExceptionStatus.Proposed,
|
||||
expiresAt: DateTimeOffset.UtcNow.AddDays(30));
|
||||
|
||||
// Act & Assert
|
||||
exception.IsEffective.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_IsEffective_WhenRevoked_ShouldBeFalse()
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(
|
||||
status: ExceptionStatus.Revoked,
|
||||
expiresAt: DateTimeOffset.UtcNow.AddDays(30));
|
||||
|
||||
// Act & Assert
|
||||
exception.IsEffective.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_IsEffective_WhenExpiredStatus_ShouldBeFalse()
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(
|
||||
status: ExceptionStatus.Expired,
|
||||
expiresAt: DateTimeOffset.UtcNow.AddDays(-1));
|
||||
|
||||
// Act & Assert
|
||||
exception.IsEffective.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ExceptionStatus.Proposed)]
|
||||
[InlineData(ExceptionStatus.Approved)]
|
||||
[InlineData(ExceptionStatus.Active)]
|
||||
[InlineData(ExceptionStatus.Expired)]
|
||||
[InlineData(ExceptionStatus.Revoked)]
|
||||
public void ExceptionStatus_AllValues_ShouldBeRecognized(ExceptionStatus status)
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(status: status);
|
||||
|
||||
// Assert
|
||||
exception.Status.Should().Be(status);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ExceptionType.Vulnerability)]
|
||||
[InlineData(ExceptionType.Policy)]
|
||||
[InlineData(ExceptionType.Unknown)]
|
||||
[InlineData(ExceptionType.Component)]
|
||||
public void ExceptionType_AllValues_ShouldBeRecognized(ExceptionType type)
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(type: type);
|
||||
|
||||
// Assert
|
||||
exception.Type.Should().Be(type);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ExceptionReason.FalsePositive)]
|
||||
[InlineData(ExceptionReason.AcceptedRisk)]
|
||||
[InlineData(ExceptionReason.CompensatingControl)]
|
||||
[InlineData(ExceptionReason.TestOnly)]
|
||||
[InlineData(ExceptionReason.VendorNotAffected)]
|
||||
[InlineData(ExceptionReason.ScheduledFix)]
|
||||
[InlineData(ExceptionReason.DeprecationInProgress)]
|
||||
[InlineData(ExceptionReason.RuntimeMitigation)]
|
||||
[InlineData(ExceptionReason.NetworkIsolation)]
|
||||
[InlineData(ExceptionReason.Other)]
|
||||
public void ExceptionReason_AllValues_ShouldBeRecognized(ExceptionReason reason)
|
||||
{
|
||||
// Arrange
|
||||
var exception = CreateException(reason: reason);
|
||||
|
||||
// Assert
|
||||
exception.ReasonCode.Should().Be(reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_WithMultipleApprovers_ShouldStoreAll()
|
||||
{
|
||||
// Arrange
|
||||
var approvers = ImmutableArray.Create("approver1", "approver2", "approver3");
|
||||
var exception = CreateException(approverIds: approvers);
|
||||
|
||||
// Assert
|
||||
exception.ApproverIds.Should().HaveCount(3);
|
||||
exception.ApproverIds.Should().Contain(["approver1", "approver2", "approver3"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_WithEvidenceRefs_ShouldStoreAll()
|
||||
{
|
||||
// Arrange
|
||||
var evidenceRefs = ImmutableArray.Create(
|
||||
"sha256:evidence1hash",
|
||||
"sha256:evidence2hash");
|
||||
|
||||
var exception = CreateException(evidenceRefs: evidenceRefs);
|
||||
|
||||
// Assert
|
||||
exception.EvidenceRefs.Should().HaveCount(2);
|
||||
exception.EvidenceRefs.Should().Contain("sha256:evidence1hash");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionObject_WithMetadata_ShouldStoreKeyValuePairs()
|
||||
{
|
||||
// Arrange
|
||||
var metadata = ImmutableDictionary<string, string>.Empty
|
||||
.Add("team", "security")
|
||||
.Add("priority", "high");
|
||||
|
||||
var exception = CreateException(metadata: metadata);
|
||||
|
||||
// Assert
|
||||
exception.Metadata.Should().HaveCount(2);
|
||||
exception.Metadata["team"].Should().Be("security");
|
||||
exception.Metadata["priority"].Should().Be("high");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionScope_WithEnvironments_ShouldStoreAll()
|
||||
{
|
||||
// Arrange
|
||||
var scope = new ExceptionScope
|
||||
{
|
||||
VulnerabilityId = "CVE-2024-12345",
|
||||
Environments = ["prod", "staging", "dev"]
|
||||
};
|
||||
|
||||
// Assert
|
||||
scope.Environments.Should().HaveCount(3);
|
||||
scope.Environments.Should().Contain(["prod", "staging", "dev"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionScope_WithTenantId_ShouldStoreValue()
|
||||
{
|
||||
// Arrange
|
||||
var tenantId = Guid.NewGuid();
|
||||
var scope = new ExceptionScope
|
||||
{
|
||||
VulnerabilityId = "CVE-2024-12345",
|
||||
TenantId = tenantId
|
||||
};
|
||||
|
||||
// Assert
|
||||
scope.TenantId.Should().Be(tenantId);
|
||||
}
|
||||
|
||||
#region Test Helpers
|
||||
|
||||
private static ExceptionObject CreateException(
|
||||
ExceptionStatus status = ExceptionStatus.Active,
|
||||
ExceptionType type = ExceptionType.Vulnerability,
|
||||
ExceptionReason reason = ExceptionReason.AcceptedRisk,
|
||||
DateTimeOffset? expiresAt = null,
|
||||
ImmutableArray<string>? approverIds = null,
|
||||
ImmutableArray<string>? evidenceRefs = null,
|
||||
ImmutableDictionary<string, string>? metadata = null)
|
||||
{
|
||||
return new ExceptionObject
|
||||
{
|
||||
ExceptionId = $"EXC-{Guid.NewGuid():N}",
|
||||
Version = 1,
|
||||
Status = status,
|
||||
Type = type,
|
||||
Scope = new ExceptionScope
|
||||
{
|
||||
VulnerabilityId = "CVE-2024-12345"
|
||||
},
|
||||
OwnerId = "owner@example.com",
|
||||
RequesterId = "requester@example.com",
|
||||
ApproverIds = approverIds ?? [],
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
ExpiresAt = expiresAt ?? DateTimeOffset.UtcNow.AddDays(30),
|
||||
ReasonCode = reason,
|
||||
Rationale = "This is a test rationale that meets the minimum character requirement of 50 characters.",
|
||||
EvidenceRefs = evidenceRefs ?? [],
|
||||
CompensatingControls = [],
|
||||
Metadata = metadata ?? ImmutableDictionary<string, string>.Empty
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user