Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Sources.Tests/Domain/SbomSourceTests.cs
2025-12-29 08:39:52 +02:00

233 lines
6.8 KiB
C#

using System.Text.Json;
using FluentAssertions;
using StellaOps.Scanner.Sources.Domain;
using Xunit;
namespace StellaOps.Scanner.Sources.Tests.Domain;
public class SbomSourceTests
{
private static readonly JsonDocument SampleConfig = JsonDocument.Parse("""
{
"registryType": "Harbor",
"registryUrl": "https://harbor.example.com"
}
""");
[Fact]
public void Create_WithValidInputs_CreatesSourceInDraftStatus()
{
// Arrange & Act
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Zastava,
configuration: SampleConfig,
createdBy: "user-1");
// Assert
source.SourceId.Should().NotBeEmpty();
source.TenantId.Should().Be("tenant-1");
source.Name.Should().Be("test-source");
source.SourceType.Should().Be(SbomSourceType.Zastava);
source.Status.Should().Be(SbomSourceStatus.Draft);
source.CreatedBy.Should().Be("user-1");
source.Paused.Should().BeFalse();
source.ConsecutiveFailures.Should().Be(0);
}
[Fact]
public void Create_WithCronSchedule_CalculatesNextScheduledRun()
{
// Arrange & Act
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "scheduled-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1",
cronSchedule: "0 * * * *"); // Every hour
// Assert
source.CronSchedule.Should().Be("0 * * * *");
source.NextScheduledRun.Should().NotBeNull();
source.NextScheduledRun.Should().BeAfter(DateTimeOffset.UtcNow);
}
[Fact]
public void Create_WithZastavaType_GeneratesWebhookEndpointAndSecret()
{
// Arrange & Act
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "webhook-source",
sourceType: SbomSourceType.Zastava,
configuration: SampleConfig,
createdBy: "user-1");
// Assert
source.WebhookEndpoint.Should().NotBeNullOrEmpty();
source.WebhookSecret.Should().NotBeNullOrEmpty();
source.WebhookSecret!.Length.Should().BeGreaterOrEqualTo(32);
}
[Fact]
public void Activate_FromDraft_ChangesStatusToActive()
{
// Arrange
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1");
// Act
source.Activate("activator");
// Assert
source.Status.Should().Be(SbomSourceStatus.Active);
source.UpdatedBy.Should().Be("activator");
}
[Fact]
public void Pause_WhenActive_PausesSource()
{
// Arrange
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1");
source.Activate("activator");
// Act
source.Pause("Maintenance window", "TICKET-123", "operator");
// Assert
source.Paused.Should().BeTrue();
source.PauseReason.Should().Be("Maintenance window");
source.PauseTicket.Should().Be("TICKET-123");
source.PausedAt.Should().NotBeNull();
}
[Fact]
public void Resume_WhenPaused_UnpausesSource()
{
// Arrange
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1");
source.Activate("activator");
source.Pause("Maintenance", null, "operator");
// Act
source.Resume("operator");
// Assert
source.Paused.Should().BeFalse();
source.PauseReason.Should().BeNull();
source.PausedAt.Should().BeNull();
}
[Fact]
public void RecordSuccessfulRun_ResetsConsecutiveFailures()
{
// Arrange
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1");
source.Activate("activator");
// Simulate some failures
source.RecordFailedRun("Error 1");
source.RecordFailedRun("Error 2");
source.ConsecutiveFailures.Should().Be(2);
// Act
source.RecordSuccessfulRun();
// Assert
source.ConsecutiveFailures.Should().Be(0);
source.LastRunStatus.Should().Be(SbomSourceRunStatus.Succeeded);
source.LastRunError.Should().BeNull();
}
[Fact]
public void RecordFailedRun_MultipleTimes_MovesToErrorStatus()
{
// Arrange
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1");
source.Activate("activator");
// Act - fail 5 times (threshold is 5)
for (var i = 0; i < 5; i++)
{
source.RecordFailedRun($"Error {i + 1}");
}
// Assert
source.Status.Should().Be(SbomSourceStatus.Error);
source.ConsecutiveFailures.Should().Be(5);
}
[Fact]
public void IsRateLimited_WhenUnderLimit_ReturnsFalse()
{
// Arrange
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1");
source.MaxScansPerHour = 10;
source.Activate("activator");
// Act
var isLimited = source.IsRateLimited();
// Assert
isLimited.Should().BeFalse();
}
[Fact]
public void UpdateConfiguration_ChangesConfigAndUpdatesTimestamp()
{
// Arrange
var source = SbomSource.Create(
tenantId: "tenant-1",
name: "test-source",
sourceType: SbomSourceType.Docker,
configuration: SampleConfig,
createdBy: "user-1");
var newConfig = JsonDocument.Parse("""
{
"registryType": "DockerHub",
"registryUrl": "https://registry-1.docker.io"
}
""");
// Act
source.UpdateConfiguration(newConfig, "updater");
// Assert
source.Configuration.RootElement.GetProperty("registryType").GetString()
.Should().Be("DockerHub");
source.UpdatedBy.Should().Be("updater");
}
}