629 lines
20 KiB
C#
629 lines
20 KiB
C#
// -----------------------------------------------------------------------------
|
|
// GatingReasonServiceTests.cs
|
|
// Sprint: SPRINT_9200_0001_0001_SCANNER_gated_triage_contracts
|
|
// Tasks: GTR-9200-019, GTR-9200-020, GTR-9200-021
|
|
// Description: Unit tests for gating reason logic, bucket counting, and VEX trust.
|
|
// Tests the gating contract DTOs and their expected behavior.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using FluentAssertions;
|
|
using StellaOps.Scanner.Triage.Entities;
|
|
using StellaOps.Scanner.WebService.Contracts;
|
|
using Xunit;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Scanner.WebService.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for gating contracts and gating reason logic.
|
|
/// Covers GTR-9200-019 (all gating reason paths), GTR-9200-020 (bucket counting),
|
|
/// and GTR-9200-021 (VEX trust threshold comparison).
|
|
/// </summary>
|
|
public sealed class GatingReasonServiceTests
|
|
{
|
|
#region GTR-9200-019: Gating Reason Path Tests - Entity Model Validation
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData(GatingReason.None, false)]
|
|
[InlineData(GatingReason.Unreachable, true)]
|
|
[InlineData(GatingReason.PolicyDismissed, true)]
|
|
[InlineData(GatingReason.Backported, true)]
|
|
[InlineData(GatingReason.VexNotAffected, true)]
|
|
[InlineData(GatingReason.Superseded, true)]
|
|
[InlineData(GatingReason.UserMuted, true)]
|
|
public void FindingGatingStatusDto_IsHiddenByDefault_MatchesGatingReason(
|
|
GatingReason reason, bool expectedHidden)
|
|
{
|
|
// Arrange & Act
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = reason,
|
|
IsHiddenByDefault = reason != GatingReason.None
|
|
};
|
|
|
|
// Assert
|
|
dto.IsHiddenByDefault.Should().Be(expectedHidden);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingGatingStatusDto_UserMuted_HasExpectedExplanation()
|
|
{
|
|
// Arrange
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.UserMuted,
|
|
IsHiddenByDefault = true,
|
|
GatingExplanation = "This finding has been muted by a user decision.",
|
|
WouldShowIf = new[] { "Un-mute the finding in triage settings" }
|
|
};
|
|
|
|
// Assert
|
|
dto.GatingExplanation.Should().Contain("muted");
|
|
dto.WouldShowIf.Should().ContainSingle();
|
|
dto.WouldShowIf.Should().Contain("Un-mute the finding in triage settings");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingGatingStatusDto_PolicyDismissed_HasPolicyIdInExplanation()
|
|
{
|
|
// Arrange
|
|
var policyId = "security-policy-v1";
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.PolicyDismissed,
|
|
IsHiddenByDefault = true,
|
|
GatingExplanation = $"Policy '{policyId}' dismissed this finding: Low risk tolerance",
|
|
WouldShowIf = new[] { "Update policy to remove dismissal rule", "Remove policy exception" }
|
|
};
|
|
|
|
// Assert
|
|
dto.GatingExplanation.Should().Contain(policyId);
|
|
dto.WouldShowIf.Should().HaveCount(2);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingGatingStatusDto_VexNotAffected_IncludesTrustInfo()
|
|
{
|
|
// Arrange
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.VexNotAffected,
|
|
IsHiddenByDefault = true,
|
|
GatingExplanation = "VEX statement from 'redhat' declares not_affected (trust: 95%)",
|
|
WouldShowIf = new[] { "Contest the VEX statement", "Lower trust threshold in policy" }
|
|
};
|
|
|
|
// Assert
|
|
dto.GatingExplanation.Should().Contain("redhat");
|
|
dto.GatingExplanation.Should().Contain("trust");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingGatingStatusDto_Backported_IncludesFixedVersion()
|
|
{
|
|
// Arrange
|
|
var fixedVersion = "1.2.3-ubuntu1";
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.Backported,
|
|
IsHiddenByDefault = true,
|
|
GatingExplanation = $"Vulnerability is fixed via distro backport in version {fixedVersion}.",
|
|
WouldShowIf = new[] { "Override backport detection", "Report false positive in backport fix" }
|
|
};
|
|
|
|
// Assert
|
|
dto.GatingExplanation.Should().Contain(fixedVersion);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingGatingStatusDto_Superseded_IncludesSupersedingCve()
|
|
{
|
|
// Arrange
|
|
var supersedingCve = "CVE-2024-9999";
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.Superseded,
|
|
IsHiddenByDefault = true,
|
|
GatingExplanation = $"This CVE has been superseded by {supersedingCve}.",
|
|
WouldShowIf = new[] { "Show superseded CVEs in settings" }
|
|
};
|
|
|
|
// Assert
|
|
dto.GatingExplanation.Should().Contain(supersedingCve);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingGatingStatusDto_Unreachable_HasSubgraphId()
|
|
{
|
|
// Arrange
|
|
var subgraphId = "sha256:subgraph123";
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.Unreachable,
|
|
IsHiddenByDefault = true,
|
|
SubgraphId = subgraphId,
|
|
GatingExplanation = "Vulnerable code is not reachable from any application entrypoint.",
|
|
WouldShowIf = new[] { "Add new entrypoint trace", "Enable 'show unreachable' filter" }
|
|
};
|
|
|
|
// Assert
|
|
dto.SubgraphId.Should().Be(subgraphId);
|
|
dto.GatingExplanation.Should().Contain("not reachable");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingGatingStatusDto_None_IsNotHidden()
|
|
{
|
|
// Arrange
|
|
var dto = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.None,
|
|
IsHiddenByDefault = false
|
|
};
|
|
|
|
// Assert
|
|
dto.IsHiddenByDefault.Should().BeFalse();
|
|
dto.GatingExplanation.Should().BeNull();
|
|
dto.WouldShowIf.Should().BeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GTR-9200-020: Bucket Counting Logic Tests
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void GatedBucketsSummaryDto_Empty_ReturnsZeroCounts()
|
|
{
|
|
// Arrange & Act
|
|
var dto = GatedBucketsSummaryDto.Empty;
|
|
|
|
// Assert
|
|
dto.UnreachableCount.Should().Be(0);
|
|
dto.PolicyDismissedCount.Should().Be(0);
|
|
dto.BackportedCount.Should().Be(0);
|
|
dto.VexNotAffectedCount.Should().Be(0);
|
|
dto.SupersededCount.Should().Be(0);
|
|
dto.UserMutedCount.Should().Be(0);
|
|
dto.TotalHiddenCount.Should().Be(0);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void GatedBucketsSummaryDto_TotalHiddenCount_SumsAllBuckets()
|
|
{
|
|
// Arrange
|
|
var dto = new GatedBucketsSummaryDto
|
|
{
|
|
UnreachableCount = 10,
|
|
PolicyDismissedCount = 5,
|
|
BackportedCount = 3,
|
|
VexNotAffectedCount = 7,
|
|
SupersededCount = 2,
|
|
UserMutedCount = 1
|
|
};
|
|
|
|
// Assert
|
|
dto.TotalHiddenCount.Should().Be(28);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void GatedBucketsSummaryDto_WithMixedCounts_CalculatesCorrectly()
|
|
{
|
|
// Arrange
|
|
var dto = new GatedBucketsSummaryDto
|
|
{
|
|
UnreachableCount = 15,
|
|
PolicyDismissedCount = 3,
|
|
BackportedCount = 7,
|
|
VexNotAffectedCount = 12,
|
|
SupersededCount = 2,
|
|
UserMutedCount = 5
|
|
};
|
|
|
|
// Assert
|
|
dto.TotalHiddenCount.Should().Be(44);
|
|
dto.UnreachableCount.Should().Be(15);
|
|
dto.VexNotAffectedCount.Should().Be(12);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void BulkTriageQueryWithGatingResponseDto_IncludesGatedBuckets()
|
|
{
|
|
// Arrange
|
|
var dto = new BulkTriageQueryWithGatingResponseDto
|
|
{
|
|
TotalCount = 100,
|
|
VisibleCount = 72,
|
|
GatedBuckets = new GatedBucketsSummaryDto
|
|
{
|
|
UnreachableCount = 15,
|
|
PolicyDismissedCount = 5,
|
|
BackportedCount = 3,
|
|
VexNotAffectedCount = 5
|
|
},
|
|
Findings = Array.Empty<FindingTriageStatusWithGatingDto>()
|
|
};
|
|
|
|
// Assert
|
|
dto.TotalCount.Should().Be(100);
|
|
dto.VisibleCount.Should().Be(72);
|
|
dto.GatedBuckets.Should().NotBeNull();
|
|
dto.GatedBuckets!.TotalHiddenCount.Should().Be(28);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void BulkTriageQueryWithGatingRequestDto_SupportsGatingReasonFilter()
|
|
{
|
|
// Arrange
|
|
var dto = new BulkTriageQueryWithGatingRequestDto
|
|
{
|
|
Query = new BulkTriageQueryRequestDto(),
|
|
IncludeHidden = true,
|
|
GatingReasonFilter = new[] { GatingReason.Unreachable, GatingReason.VexNotAffected }
|
|
};
|
|
|
|
// Assert
|
|
dto.IncludeHidden.Should().BeTrue();
|
|
dto.GatingReasonFilter.Should().HaveCount(2);
|
|
dto.GatingReasonFilter.Should().Contain(GatingReason.Unreachable);
|
|
dto.GatingReasonFilter.Should().Contain(GatingReason.VexNotAffected);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void BulkTriageQueryWithGatingRequestDto_DefaultsToNotIncludeHidden()
|
|
{
|
|
// Arrange
|
|
var dto = new BulkTriageQueryWithGatingRequestDto
|
|
{
|
|
Query = new BulkTriageQueryRequestDto()
|
|
};
|
|
|
|
// Assert
|
|
dto.IncludeHidden.Should().BeFalse();
|
|
dto.GatingReasonFilter.Should().BeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GTR-9200-021: VEX Trust Threshold Comparison Tests
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexTrustBreakdownDto_AllComponents_SumToCompositeScore()
|
|
{
|
|
// Arrange - weights: issuer=0.4, recency=0.2, justification=0.2, evidence=0.2
|
|
var dto = new VexTrustBreakdownDto
|
|
{
|
|
IssuerTrust = 1.0, // Max issuer trust (NVD)
|
|
RecencyTrust = 1.0, // Very recent
|
|
JustificationTrust = 1.0, // Detailed justification
|
|
EvidenceTrust = 1.0 // Signed with ledger
|
|
};
|
|
|
|
// Assert - all max values = composite score of 1.0
|
|
var compositeScore = (dto.IssuerTrust * 0.4) +
|
|
(dto.RecencyTrust * 0.2) +
|
|
(dto.JustificationTrust * 0.2) +
|
|
(dto.EvidenceTrust * 0.2);
|
|
compositeScore.Should().Be(1.0);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexTrustBreakdownDto_LowIssuerTrust_ReducesCompositeScore()
|
|
{
|
|
// Arrange - unknown issuer has low trust (0.5)
|
|
var dto = new VexTrustBreakdownDto
|
|
{
|
|
IssuerTrust = 0.5, // Unknown issuer
|
|
RecencyTrust = 1.0,
|
|
JustificationTrust = 1.0,
|
|
EvidenceTrust = 1.0
|
|
};
|
|
|
|
// Assert
|
|
var compositeScore = (dto.IssuerTrust * 0.4) +
|
|
(dto.RecencyTrust * 0.2) +
|
|
(dto.JustificationTrust * 0.2) +
|
|
(dto.EvidenceTrust * 0.2);
|
|
compositeScore.Should().Be(0.8);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void TriageVexTrustStatusDto_MeetsPolicyThreshold_WhenTrustExceedsThreshold()
|
|
{
|
|
// Arrange
|
|
var dto = new TriageVexTrustStatusDto
|
|
{
|
|
VexStatus = new TriageVexStatusDto { Status = "not_affected" },
|
|
TrustScore = 0.85,
|
|
PolicyTrustThreshold = 0.7,
|
|
MeetsPolicyThreshold = true
|
|
};
|
|
|
|
// Assert
|
|
dto.TrustScore.Should().NotBeNull();
|
|
dto.PolicyTrustThreshold.Should().NotBeNull();
|
|
dto.TrustScore!.Value.Should().BeGreaterThan(dto.PolicyTrustThreshold!.Value);
|
|
dto.MeetsPolicyThreshold.Should().BeTrue();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void TriageVexTrustStatusDto_DoesNotMeetThreshold_WhenTrustBelowThreshold()
|
|
{
|
|
// Arrange
|
|
var dto = new TriageVexTrustStatusDto
|
|
{
|
|
VexStatus = new TriageVexStatusDto { Status = "not_affected" },
|
|
TrustScore = 0.5,
|
|
PolicyTrustThreshold = 0.7,
|
|
MeetsPolicyThreshold = false
|
|
};
|
|
|
|
// Assert
|
|
dto.TrustScore.Should().NotBeNull();
|
|
dto.PolicyTrustThreshold.Should().NotBeNull();
|
|
dto.TrustScore!.Value.Should().BeLessThan(dto.PolicyTrustThreshold!.Value);
|
|
dto.MeetsPolicyThreshold.Should().BeFalse();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData("nvd", 1.0)]
|
|
[InlineData("redhat", 0.95)]
|
|
[InlineData("canonical", 0.95)]
|
|
[InlineData("debian", 0.95)]
|
|
[InlineData("suse", 0.9)]
|
|
[InlineData("microsoft", 0.9)]
|
|
public void VexIssuerTrust_KnownIssuers_HaveExpectedTrustScores(string _, double expectedTrust)
|
|
{
|
|
// This test documents the expected trust scores for known issuers
|
|
// The actual implementation is in GatingReasonService.GetIssuerTrust()
|
|
expectedTrust.Should().BeGreaterThanOrEqualTo(0.9);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexRecencyTrust_RecentStatement_HasHighTrust()
|
|
{
|
|
// Arrange - VEX from within a week
|
|
var validFrom = DateTimeOffset.UtcNow.AddDays(-3);
|
|
var age = DateTimeOffset.UtcNow - validFrom;
|
|
|
|
// Assert - within a week = trust 1.0
|
|
age.TotalDays.Should().BeLessThan(7);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexRecencyTrust_OldStatement_HasLowTrust()
|
|
{
|
|
// Arrange - VEX from over a year ago
|
|
var validFrom = DateTimeOffset.UtcNow.AddYears(-2);
|
|
var age = DateTimeOffset.UtcNow - validFrom;
|
|
|
|
// Assert - over a year = trust 0.3
|
|
age.TotalDays.Should().BeGreaterThan(365);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexJustificationTrust_DetailedJustification_HasHighTrust()
|
|
{
|
|
// Arrange - 500+ chars = trust 1.0
|
|
var justification = new string('x', 600);
|
|
|
|
// Assert
|
|
justification.Length.Should().BeGreaterThanOrEqualTo(500);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexJustificationTrust_ShortJustification_HasLowTrust()
|
|
{
|
|
// Arrange - < 50 chars = trust 0.4
|
|
var justification = "short";
|
|
|
|
// Assert
|
|
justification.Length.Should().BeLessThan(50);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexEvidenceTrust_SignedWithLedger_HasHighTrust()
|
|
{
|
|
// Arrange - DSSE envelope + signature ref + source ref
|
|
var now = DateTimeOffset.UtcNow;
|
|
var vex = new TriageEffectiveVex
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Status = TriageVexStatus.NotAffected,
|
|
DsseEnvelopeHash = "sha256:signed",
|
|
SignatureRef = "ledger-entry",
|
|
SourceDomain = "nvd",
|
|
SourceRef = "NVD-CVE-2024-1234",
|
|
ValidFrom = now,
|
|
CollectedAt = now
|
|
};
|
|
|
|
// Assert - all evidence factors present
|
|
vex.DsseEnvelopeHash.Should().NotBeNull();
|
|
vex.SignatureRef.Should().NotBeNull();
|
|
vex.SourceRef.Should().NotBeNull();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void VexEvidenceTrust_NoEvidence_HasBaseTrust()
|
|
{
|
|
// Arrange - no signature, no ledger, no source
|
|
var now = DateTimeOffset.UtcNow;
|
|
var vex = new TriageEffectiveVex
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Status = TriageVexStatus.NotAffected,
|
|
DsseEnvelopeHash = null,
|
|
SignatureRef = null,
|
|
SourceDomain = "unknown",
|
|
SourceRef = "unknown",
|
|
ValidFrom = now,
|
|
CollectedAt = now
|
|
};
|
|
|
|
// Assert - base trust only
|
|
vex.DsseEnvelopeHash.Should().BeNull();
|
|
vex.SignatureRef.Should().BeNull();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Edge Cases and Entity Model Validation
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void TriageFinding_RequiredFields_AreSet()
|
|
{
|
|
// Arrange
|
|
var now = DateTimeOffset.UtcNow;
|
|
var finding = new TriageFinding
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
AssetLabel = "test-asset",
|
|
Purl = "pkg:npm/test@1.0.0",
|
|
CveId = "CVE-2024-1234",
|
|
FirstSeenAt = now,
|
|
LastSeenAt = now,
|
|
UpdatedAt = now
|
|
};
|
|
|
|
// Assert
|
|
finding.AssetLabel.Should().NotBeNullOrEmpty();
|
|
finding.Purl.Should().NotBeNullOrEmpty();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void TriagePolicyDecision_PolicyActions_AreValid()
|
|
{
|
|
// Valid actions: dismiss, waive, tolerate, block
|
|
var validActions = new[] { "dismiss", "waive", "tolerate", "block" };
|
|
|
|
foreach (var action in validActions)
|
|
{
|
|
var decision = new TriagePolicyDecision
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
PolicyId = "test-policy",
|
|
Action = action,
|
|
AppliedAt = DateTimeOffset.UtcNow
|
|
};
|
|
|
|
decision.Action.Should().Be(action);
|
|
}
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void TriageEffectiveVex_VexStatuses_AreAllDefined()
|
|
{
|
|
// Arrange
|
|
var statuses = Enum.GetValues<TriageVexStatus>();
|
|
|
|
// Assert - all expected statuses exist
|
|
statuses.Should().Contain(TriageVexStatus.NotAffected);
|
|
statuses.Should().Contain(TriageVexStatus.Affected);
|
|
statuses.Should().Contain(TriageVexStatus.UnderInvestigation);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void TriageReachability_Values_AreAllDefined()
|
|
{
|
|
// Arrange
|
|
var values = Enum.GetValues<TriageReachability>();
|
|
|
|
// Assert
|
|
values.Should().Contain(TriageReachability.Yes);
|
|
values.Should().Contain(TriageReachability.No);
|
|
values.Should().Contain(TriageReachability.Unknown);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void TriageReachabilityResult_RequiredInputsHash_IsSet()
|
|
{
|
|
// Arrange
|
|
var result = new TriageReachabilityResult
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Reachable = TriageReachability.No,
|
|
InputsHash = "sha256:inputs-hash",
|
|
SubgraphId = "sha256:subgraph",
|
|
ComputedAt = DateTimeOffset.UtcNow
|
|
};
|
|
|
|
// Assert
|
|
result.InputsHash.Should().NotBeNullOrEmpty();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void GatingReason_AllValues_HaveCorrectNumericMapping()
|
|
{
|
|
// Document the enum values for API stability
|
|
GatingReason.None.Should().Be((GatingReason)0);
|
|
GatingReason.Unreachable.Should().Be((GatingReason)1);
|
|
GatingReason.PolicyDismissed.Should().Be((GatingReason)2);
|
|
GatingReason.Backported.Should().Be((GatingReason)3);
|
|
GatingReason.VexNotAffected.Should().Be((GatingReason)4);
|
|
GatingReason.Superseded.Should().Be((GatingReason)5);
|
|
GatingReason.UserMuted.Should().Be((GatingReason)6);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void FindingTriageStatusWithGatingDto_CombinesBaseStatusWithGating()
|
|
{
|
|
// Arrange
|
|
var baseStatus = new FindingTriageStatusDto
|
|
{
|
|
FindingId = Guid.NewGuid().ToString(),
|
|
Lane = "high",
|
|
Verdict = "Block"
|
|
};
|
|
var gating = new FindingGatingStatusDto
|
|
{
|
|
GatingReason = GatingReason.Unreachable,
|
|
IsHiddenByDefault = true
|
|
};
|
|
|
|
var dto = new FindingTriageStatusWithGatingDto
|
|
{
|
|
BaseStatus = baseStatus,
|
|
Gating = gating
|
|
};
|
|
|
|
// Assert
|
|
dto.BaseStatus.Should().NotBeNull();
|
|
dto.Gating.Should().NotBeNull();
|
|
dto.Gating!.GatingReason.Should().Be(GatingReason.Unreachable);
|
|
}
|
|
|
|
#endregion
|
|
}
|