252 lines
7.7 KiB
C#
252 lines
7.7 KiB
C#
using System.Text.Json;
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.Time.Testing;
|
|
using StellaOps.Scanner.Sources.Domain;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Scanner.Sources.Tests.Domain;
|
|
|
|
public class SbomSourceTests
|
|
{
|
|
private readonly FakeTimeProvider _timeProvider;
|
|
|
|
public SbomSourceTests()
|
|
{
|
|
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 15, 10, 0, 0, TimeSpan.Zero));
|
|
}
|
|
|
|
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",
|
|
timeProvider: _timeProvider);
|
|
|
|
// 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.Pending);
|
|
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",
|
|
timeProvider: _timeProvider,
|
|
cronSchedule: "0 * * * *"); // Every hour
|
|
|
|
// Assert
|
|
source.CronSchedule.Should().Be("0 * * * *");
|
|
source.NextScheduledRun.Should().NotBeNull();
|
|
source.NextScheduledRun.Should().BeAfter(_timeProvider.GetUtcNow());
|
|
}
|
|
|
|
[Fact]
|
|
public void Create_WithZastavaType_GeneratesWebhookEndpoint()
|
|
{
|
|
// Arrange & Act
|
|
var source = SbomSource.Create(
|
|
tenantId: "tenant-1",
|
|
name: "webhook-source",
|
|
sourceType: SbomSourceType.Zastava,
|
|
configuration: SampleConfig,
|
|
createdBy: "user-1",
|
|
timeProvider: _timeProvider);
|
|
|
|
// Assert
|
|
source.WebhookEndpoint.Should().NotBeNullOrEmpty();
|
|
source.WebhookSecretRef.Should().NotBeNullOrEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Activate_FromPending_ChangesStatusToActive()
|
|
{
|
|
// Arrange
|
|
var source = SbomSource.Create(
|
|
tenantId: "tenant-1",
|
|
name: "test-source",
|
|
sourceType: SbomSourceType.Docker,
|
|
configuration: SampleConfig,
|
|
createdBy: "user-1",
|
|
timeProvider: _timeProvider);
|
|
|
|
// Act
|
|
source.Activate("activator", _timeProvider);
|
|
|
|
// 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",
|
|
timeProvider: _timeProvider);
|
|
source.Activate("activator", _timeProvider);
|
|
|
|
// Act
|
|
source.Pause("Maintenance window", "TICKET-123", "operator", _timeProvider);
|
|
|
|
// 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",
|
|
timeProvider: _timeProvider);
|
|
source.Activate("activator", _timeProvider);
|
|
source.Pause("Maintenance", null, "operator", _timeProvider);
|
|
|
|
// Act
|
|
source.Resume("operator", _timeProvider);
|
|
|
|
// 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",
|
|
timeProvider: _timeProvider);
|
|
source.Activate("activator", _timeProvider);
|
|
|
|
// Simulate some failures
|
|
var runAt = _timeProvider.GetUtcNow();
|
|
source.RecordFailedRun(runAt, "Error 1", _timeProvider);
|
|
source.RecordFailedRun(runAt, "Error 2", _timeProvider);
|
|
source.ConsecutiveFailures.Should().Be(2);
|
|
|
|
// Act
|
|
source.RecordSuccessfulRun(runAt, _timeProvider);
|
|
|
|
// 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",
|
|
timeProvider: _timeProvider);
|
|
source.Activate("activator", _timeProvider);
|
|
|
|
// Act - fail multiple times
|
|
var runAt = _timeProvider.GetUtcNow();
|
|
for (var i = 0; i < 5; i++)
|
|
{
|
|
source.RecordFailedRun(runAt, $"Error {i + 1}", _timeProvider);
|
|
}
|
|
|
|
// 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",
|
|
timeProvider: _timeProvider);
|
|
source.MaxScansPerHour = 10;
|
|
source.Activate("activator", _timeProvider);
|
|
|
|
// Act
|
|
var isLimited = source.IsRateLimited(_timeProvider);
|
|
|
|
// 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",
|
|
timeProvider: _timeProvider);
|
|
|
|
var newConfig = JsonDocument.Parse("""
|
|
{
|
|
"registryType": "DockerHub",
|
|
"registryUrl": "https://registry-1.docker.io"
|
|
}
|
|
""");
|
|
|
|
// Act
|
|
source.UpdateConfiguration(newConfig, "updater", _timeProvider);
|
|
|
|
// Assert
|
|
source.Configuration.RootElement.GetProperty("registryType").GetString()
|
|
.Should().Be("DockerHub");
|
|
source.UpdatedBy.Should().Be("updater");
|
|
}
|
|
}
|