Frontend gaps fill work. Testing fixes work. Auditing in progress.
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Unit tests for RegistrySourceService
|
||||
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.SbomService.Models;
|
||||
using StellaOps.SbomService.Repositories;
|
||||
using StellaOps.SbomService.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.SbomService.Tests;
|
||||
|
||||
public class RegistrySourceServiceTests
|
||||
{
|
||||
private readonly Mock<IRegistrySourceRepository> _sourceRepoMock;
|
||||
private readonly Mock<IRegistrySourceRunRepository> _runRepoMock;
|
||||
private readonly RegistrySourceService _service;
|
||||
|
||||
public RegistrySourceServiceTests()
|
||||
{
|
||||
_sourceRepoMock = new Mock<IRegistrySourceRepository>();
|
||||
_runRepoMock = new Mock<IRegistrySourceRunRepository>();
|
||||
|
||||
_service = new RegistrySourceService(
|
||||
_sourceRepoMock.Object,
|
||||
_runRepoMock.Object,
|
||||
NullLogger<RegistrySourceService>.Instance);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task CreateAsync_WithValidRequest_CreatesRegistrySource()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateRegistrySourceRequest(
|
||||
Name: "Test Registry",
|
||||
Description: "Test description",
|
||||
Type: RegistrySourceType.Harbor,
|
||||
RegistryUrl: "https://harbor.example.com",
|
||||
AuthRefUri: "authref://vault/harbor#credentials",
|
||||
IntegrationId: null,
|
||||
RepoFilters: ["myorg/*"],
|
||||
TagFilters: null,
|
||||
TriggerMode: RegistryTriggerMode.Webhook,
|
||||
ScheduleCron: null,
|
||||
WebhookSecretRefUri: "authref://vault/harbor#webhook-secret",
|
||||
Tags: ["production"]);
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<RegistrySource>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.CreateAsync(request, "user@example.com", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Name.Should().Be("Test Registry");
|
||||
result.RegistryUrl.Should().Be("https://harbor.example.com");
|
||||
result.Type.Should().Be(RegistrySourceType.Harbor);
|
||||
result.Status.Should().Be(RegistrySourceStatus.Pending);
|
||||
result.TriggerMode.Should().Be(RegistryTriggerMode.Webhook);
|
||||
result.RepoFilters.Should().Contain("myorg/*");
|
||||
result.CreatedBy.Should().Be("user@example.com");
|
||||
result.TenantId.Should().Be("tenant-1");
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task CreateAsync_TrimsTrailingSlashFromUrl()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateRegistrySourceRequest(
|
||||
Name: "Test",
|
||||
Description: null,
|
||||
Type: RegistrySourceType.OciGeneric,
|
||||
RegistryUrl: "https://registry.example.com/",
|
||||
AuthRefUri: null,
|
||||
IntegrationId: null,
|
||||
RepoFilters: null,
|
||||
TagFilters: null,
|
||||
TriggerMode: RegistryTriggerMode.Manual,
|
||||
ScheduleCron: null,
|
||||
WebhookSecretRefUri: null,
|
||||
Tags: null);
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<RegistrySource>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.CreateAsync(request, null, null);
|
||||
|
||||
// Assert
|
||||
result.RegistryUrl.Should().Be("https://registry.example.com");
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_WithExistingId_ReturnsSource()
|
||||
{
|
||||
// Arrange
|
||||
var source = CreateTestSource();
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(source);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetByIdAsync(source.Id);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Id.Should().Be(source.Id);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_WithNonExistingId_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((RegistrySource?)null);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetByIdAsync(id);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task ListAsync_WithTypeFilter_ReturnsFilteredResults()
|
||||
{
|
||||
// Arrange
|
||||
var harborSources = new[]
|
||||
{
|
||||
CreateTestSource(type: RegistrySourceType.Harbor),
|
||||
CreateTestSource(type: RegistrySourceType.Harbor)
|
||||
};
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetAllAsync(It.Is<RegistrySourceQuery>(q => q.Type == RegistrySourceType.Harbor), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(harborSources);
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.CountAsync(It.Is<RegistrySourceQuery>(q => q.Type == RegistrySourceType.Harbor), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(2);
|
||||
|
||||
var request = new ListRegistrySourcesRequest(Type: RegistrySourceType.Harbor);
|
||||
|
||||
// Act
|
||||
var result = await _service.ListAsync(request, null);
|
||||
|
||||
// Assert
|
||||
result.Items.Should().HaveCount(2);
|
||||
result.Items.Should().OnlyContain(s => s.Type == RegistrySourceType.Harbor);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task UpdateAsync_WithExistingSource_UpdatesFields()
|
||||
{
|
||||
// Arrange
|
||||
var source = CreateTestSource();
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(source);
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<RegistrySource>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
var request = new UpdateRegistrySourceRequest(
|
||||
Name: "Updated Name",
|
||||
Description: "Updated description",
|
||||
RegistryUrl: null,
|
||||
AuthRefUri: null,
|
||||
RepoFilters: null,
|
||||
TagFilters: null,
|
||||
TriggerMode: null,
|
||||
ScheduleCron: null,
|
||||
WebhookSecretRefUri: null,
|
||||
Status: null,
|
||||
Tags: null);
|
||||
|
||||
// Act
|
||||
var result = await _service.UpdateAsync(source.Id, request, "updater@example.com");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Name.Should().Be("Updated Name");
|
||||
result.Description.Should().Be("Updated description");
|
||||
result.UpdatedBy.Should().Be("updater@example.com");
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task UpdateAsync_WithNonExistingSource_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((RegistrySource?)null);
|
||||
|
||||
var request = new UpdateRegistrySourceRequest(
|
||||
Name: "Updated", Description: null, RegistryUrl: null, AuthRefUri: null,
|
||||
RepoFilters: null, TagFilters: null, TriggerMode: null, ScheduleCron: null,
|
||||
WebhookSecretRefUri: null, Status: null, Tags: null);
|
||||
|
||||
// Act
|
||||
var result = await _service.UpdateAsync(id, request, "user");
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task DeleteAsync_WithExistingSource_DeletesFromRepository()
|
||||
{
|
||||
// Arrange
|
||||
var source = CreateTestSource();
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(source);
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.DeleteAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _service.DeleteAsync(source.Id, "deleter@example.com");
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
_sourceRepoMock.Verify(r => r.DeleteAsync(source.Id, It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task TriggerAsync_WithActiveSource_CreatesRun()
|
||||
{
|
||||
// Arrange
|
||||
var source = CreateTestSource();
|
||||
source.Status = RegistrySourceStatus.Active;
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(source);
|
||||
|
||||
_runRepoMock
|
||||
.Setup(r => r.CreateAsync(It.IsAny<RegistrySourceRun>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<RegistrySourceRun, CancellationToken>((run, _) => Task.FromResult(run));
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<RegistrySource>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.TriggerAsync(source.Id, "manual", null, "user@example.com");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.SourceId.Should().Be(source.Id);
|
||||
result.TriggerType.Should().Be("manual");
|
||||
result.Status.Should().Be(RegistryRunStatus.Queued);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task PauseAsync_WithActiveSource_PausesSource()
|
||||
{
|
||||
// Arrange
|
||||
var source = CreateTestSource();
|
||||
source.Status = RegistrySourceStatus.Active;
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(source);
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<RegistrySource>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.PauseAsync(source.Id, "Maintenance", "admin@example.com");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Status.Should().Be(RegistrySourceStatus.Paused);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task ResumeAsync_WithPausedSource_ResumesSource()
|
||||
{
|
||||
// Arrange
|
||||
var source = CreateTestSource();
|
||||
source.Status = RegistrySourceStatus.Paused;
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(source);
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<RegistrySource>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.ResumeAsync(source.Id, "admin@example.com");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result!.Status.Should().Be(RegistrySourceStatus.Active);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task GetRunHistoryAsync_ReturnsRunsForSource()
|
||||
{
|
||||
// Arrange
|
||||
var sourceId = Guid.NewGuid();
|
||||
var runs = new[]
|
||||
{
|
||||
CreateTestRun(sourceId),
|
||||
CreateTestRun(sourceId),
|
||||
CreateTestRun(sourceId)
|
||||
};
|
||||
|
||||
_runRepoMock
|
||||
.Setup(r => r.GetBySourceIdAsync(sourceId, 50, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(runs);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetRunHistoryAsync(sourceId, 50);
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(3);
|
||||
result.Should().OnlyContain(r => r.SourceId == sourceId);
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static RegistrySource CreateTestSource(RegistrySourceType type = RegistrySourceType.Harbor) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Test Registry",
|
||||
Type = type,
|
||||
RegistryUrl = "https://test-registry.example.com",
|
||||
Status = RegistrySourceStatus.Pending,
|
||||
TriggerMode = RegistryTriggerMode.Manual
|
||||
};
|
||||
|
||||
private static RegistrySourceRun CreateTestRun(Guid sourceId) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
SourceId = sourceId,
|
||||
Status = RegistryRunStatus.Completed,
|
||||
TriggerType = "manual",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
CompletedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user