audit, advisories and doctors/setup work
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Unit tests for RegistryDiscoveryService and ScanJobEmitterService
|
||||
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using StellaOps.SbomService.Models;
|
||||
@@ -38,7 +40,12 @@ public class RegistryDiscoveryServiceTests
|
||||
_service = new RegistryDiscoveryService(
|
||||
_sourceRepoMock.Object,
|
||||
httpClientFactory.Object,
|
||||
NullLogger<RegistryDiscoveryService>.Instance);
|
||||
NullLogger<RegistryDiscoveryService>.Instance,
|
||||
Options.Create(new RegistryHttpOptions
|
||||
{
|
||||
AllowedHosts = new List<string> { "test-registry.example.com" },
|
||||
AllowInlineCredentials = true
|
||||
}));
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
@@ -61,7 +68,7 @@ public class RegistryDiscoveryServiceTests
|
||||
public async Task DiscoverRepositoriesAsync_WithUnknownSource_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var sourceId = Guid.NewGuid();
|
||||
var sourceId = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(sourceId, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((RegistrySource?)null);
|
||||
@@ -100,6 +107,26 @@ public class RegistryDiscoveryServiceTests
|
||||
result.Repositories.Should().Contain("library/nginx");
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task DiscoverRepositoriesAsync_WithDisallowedHost_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var source = CreateTestSource();
|
||||
source.RegistryUrl = "https://blocked.example.com";
|
||||
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(source.Id, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(source);
|
||||
|
||||
// Act
|
||||
var result = await _service.DiscoverRepositoriesAsync(source.Id.ToString());
|
||||
|
||||
// Assert
|
||||
result.Success.Should().BeFalse();
|
||||
result.Error.Should().Contain("allowlisted");
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task DiscoverRepositoriesAsync_WithRepositoryDenylist_ExcludesMatches()
|
||||
@@ -187,12 +214,14 @@ public class RegistryDiscoveryServiceTests
|
||||
|
||||
private static RegistrySource CreateTestSource() => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222222"),
|
||||
Name = "Test Registry",
|
||||
Type = RegistrySourceType.Harbor,
|
||||
RegistryUrl = "https://test-registry.example.com",
|
||||
AuthRefUri = "authref://vault/registry#credentials",
|
||||
Status = RegistrySourceStatus.Active
|
||||
Status = RegistrySourceStatus.Active,
|
||||
CreatedAt = DateTimeOffset.Parse("2025-12-29T12:00:00Z", CultureInfo.InvariantCulture),
|
||||
UpdatedAt = DateTimeOffset.Parse("2025-12-29T12:00:00Z", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
private void SetupHttpResponse(HttpStatusCode statusCode, string content)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Unit tests for RegistrySourceService
|
||||
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using StellaOps.SbomService.Models;
|
||||
using StellaOps.SbomService.Repositories;
|
||||
@@ -22,10 +24,22 @@ public class RegistrySourceServiceTests
|
||||
_sourceRepoMock = new Mock<IRegistrySourceRepository>();
|
||||
_runRepoMock = new Mock<IRegistrySourceRunRepository>();
|
||||
|
||||
var timeProvider = new FixedTimeProvider(FixedNow);
|
||||
var guidProvider = CreateGuidProvider();
|
||||
var httpOptions = Options.Create(new RegistryHttpOptions
|
||||
{
|
||||
AllowedHosts = new List<string> { "harbor.example.com", "registry.example.com", "test-registry.example.com" }
|
||||
});
|
||||
var queryOptions = Options.Create(new RegistrySourceQueryOptions());
|
||||
|
||||
_service = new RegistrySourceService(
|
||||
_sourceRepoMock.Object,
|
||||
_runRepoMock.Object,
|
||||
NullLogger<RegistrySourceService>.Instance);
|
||||
NullLogger<RegistrySourceService>.Instance,
|
||||
timeProvider,
|
||||
guidProvider,
|
||||
httpOptions,
|
||||
queryOptions);
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
@@ -90,7 +104,7 @@ public class RegistrySourceServiceTests
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.CreateAsync(request, null, null);
|
||||
var result = await _service.CreateAsync(request, null, "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.RegistryUrl.Should().Be("https://registry.example.com");
|
||||
@@ -107,7 +121,7 @@ public class RegistrySourceServiceTests
|
||||
.ReturnsAsync(source);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetByIdAsync(source.Id);
|
||||
var result = await _service.GetByIdAsync(source.Id, "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
@@ -125,7 +139,7 @@ public class RegistrySourceServiceTests
|
||||
.ReturnsAsync((RegistrySource?)null);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetByIdAsync(id);
|
||||
var result = await _service.GetByIdAsync(id, "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
@@ -153,7 +167,7 @@ public class RegistrySourceServiceTests
|
||||
var request = new ListRegistrySourcesRequest(Type: RegistrySourceType.Harbor);
|
||||
|
||||
// Act
|
||||
var result = await _service.ListAsync(request, null);
|
||||
var result = await _service.ListAsync(request, "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Items.Should().HaveCount(2);
|
||||
@@ -188,7 +202,7 @@ public class RegistrySourceServiceTests
|
||||
Tags: null);
|
||||
|
||||
// Act
|
||||
var result = await _service.UpdateAsync(source.Id, request, "updater@example.com");
|
||||
var result = await _service.UpdateAsync(source.Id, request, "updater@example.com", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
@@ -213,7 +227,7 @@ public class RegistrySourceServiceTests
|
||||
WebhookSecretRefUri: null, Status: null, Tags: null);
|
||||
|
||||
// Act
|
||||
var result = await _service.UpdateAsync(id, request, "user");
|
||||
var result = await _service.UpdateAsync(id, request, "user", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
@@ -234,7 +248,7 @@ public class RegistrySourceServiceTests
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _service.DeleteAsync(source.Id, "deleter@example.com");
|
||||
var result = await _service.DeleteAsync(source.Id, "deleter@example.com", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
@@ -262,7 +276,7 @@ public class RegistrySourceServiceTests
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.TriggerAsync(source.Id, "manual", null, "user@example.com");
|
||||
var result = await _service.TriggerAsync(source.Id, "manual", null, "user@example.com", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
@@ -288,7 +302,7 @@ public class RegistrySourceServiceTests
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.PauseAsync(source.Id, "Maintenance", "admin@example.com");
|
||||
var result = await _service.PauseAsync(source.Id, "Maintenance", "admin@example.com", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
@@ -312,7 +326,7 @@ public class RegistrySourceServiceTests
|
||||
.Returns<RegistrySource, CancellationToken>((s, _) => Task.FromResult(s));
|
||||
|
||||
// Act
|
||||
var result = await _service.ResumeAsync(source.Id, "admin@example.com");
|
||||
var result = await _service.ResumeAsync(source.Id, "admin@example.com", "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
@@ -337,7 +351,7 @@ public class RegistrySourceServiceTests
|
||||
.ReturnsAsync(runs);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetRunHistoryAsync(sourceId, 50);
|
||||
var result = await _service.GetRunHistoryAsync(sourceId, 50, "tenant-1");
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(3);
|
||||
@@ -348,23 +362,39 @@ public class RegistrySourceServiceTests
|
||||
|
||||
private static RegistrySource CreateTestSource(RegistrySourceType type = RegistrySourceType.Harbor) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Id = Guid.Parse("11111111-1111-1111-1111-111111111111"),
|
||||
Name = "Test Registry",
|
||||
Type = type,
|
||||
RegistryUrl = "https://test-registry.example.com",
|
||||
Status = RegistrySourceStatus.Pending,
|
||||
TriggerMode = RegistryTriggerMode.Manual
|
||||
TriggerMode = RegistryTriggerMode.Manual,
|
||||
CreatedAt = FixedNow,
|
||||
UpdatedAt = FixedNow,
|
||||
TenantId = "tenant-1"
|
||||
};
|
||||
|
||||
private static RegistrySourceRun CreateTestRun(Guid sourceId) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222222"),
|
||||
SourceId = sourceId,
|
||||
Status = RegistryRunStatus.Completed,
|
||||
TriggerType = "manual",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
CompletedAt = DateTimeOffset.UtcNow
|
||||
StartedAt = FixedNow.AddMinutes(-5),
|
||||
CompletedAt = FixedNow
|
||||
};
|
||||
|
||||
private static QueueGuidProvider CreateGuidProvider()
|
||||
{
|
||||
return new QueueGuidProvider(new[]
|
||||
{
|
||||
Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
||||
Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"),
|
||||
Guid.Parse("cccccccc-cccc-cccc-cccc-cccccccccccc")
|
||||
});
|
||||
}
|
||||
|
||||
private static DateTimeOffset FixedNow =>
|
||||
DateTimeOffset.Parse("2025-12-29T12:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// Unit tests for RegistryWebhookService
|
||||
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -28,7 +29,7 @@ public class RegistryWebhookServiceTests
|
||||
_runRepoMock = new Mock<IRegistrySourceRunRepository>();
|
||||
_sourceServiceMock = new Mock<IRegistrySourceService>();
|
||||
_clockMock = new Mock<IClock>();
|
||||
_clockMock.Setup(c => c.UtcNow).Returns(DateTimeOffset.Parse("2025-12-29T12:00:00Z"));
|
||||
_clockMock.Setup(c => c.UtcNow).Returns(FixedNow);
|
||||
|
||||
_service = new RegistryWebhookService(
|
||||
_sourceRepoMock.Object,
|
||||
@@ -59,7 +60,7 @@ public class RegistryWebhookServiceTests
|
||||
public async Task ProcessWebhookAsync_WithUnknownSource_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var sourceId = Guid.NewGuid();
|
||||
var sourceId = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||||
_sourceRepoMock
|
||||
.Setup(r => r.GetByIdAsync(sourceId, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((RegistrySource?)null);
|
||||
@@ -113,6 +114,7 @@ public class RegistryWebhookServiceTests
|
||||
"webhook",
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string?>(),
|
||||
It.IsAny<string?>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(expectedRun);
|
||||
|
||||
@@ -183,27 +185,29 @@ public class RegistryWebhookServiceTests
|
||||
|
||||
private static RegistrySource CreateTestSource() => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222222"),
|
||||
Name = "Test Harbor",
|
||||
Type = RegistrySourceType.Harbor,
|
||||
RegistryUrl = "https://harbor.example.com",
|
||||
Status = RegistrySourceStatus.Active,
|
||||
TriggerMode = RegistryTriggerMode.Webhook
|
||||
TriggerMode = RegistryTriggerMode.Webhook,
|
||||
CreatedAt = FixedNow,
|
||||
UpdatedAt = FixedNow
|
||||
};
|
||||
|
||||
private static RegistrySourceRun CreateTestRun(Guid sourceId) => new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Id = Guid.Parse("33333333-3333-3333-3333-333333333333"),
|
||||
SourceId = sourceId,
|
||||
Status = RegistryRunStatus.Running,
|
||||
TriggerType = "webhook",
|
||||
StartedAt = DateTimeOffset.UtcNow
|
||||
StartedAt = FixedNow
|
||||
};
|
||||
|
||||
private static string CreateHarborPushPayload(string repository, string tag) => JsonSerializer.Serialize(new
|
||||
{
|
||||
type = "PUSH_ARTIFACT",
|
||||
occur_at = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
occur_at = FixedNow.ToUnixTimeSeconds(),
|
||||
@operator = "admin",
|
||||
event_data = new
|
||||
{
|
||||
@@ -225,5 +229,8 @@ public class RegistryWebhookServiceTests
|
||||
}
|
||||
});
|
||||
|
||||
private static DateTimeOffset FixedNow =>
|
||||
DateTimeOffset.Parse("2025-12-29T12:00:00Z", CultureInfo.InvariantCulture);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Globalization;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.SbomService.Models;
|
||||
using StellaOps.SbomService.Repositories;
|
||||
using StellaOps.SbomService.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.SbomService.Tests;
|
||||
|
||||
public sealed class SbomLedgerServiceTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task AddVersionAsync_UsesGuidProviderAndClock()
|
||||
{
|
||||
// Arrange
|
||||
var repository = new InMemorySbomLedgerRepository();
|
||||
var clock = new FixedClock(FixedNow);
|
||||
var guidProvider = new QueueGuidProvider(new[]
|
||||
{
|
||||
Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
||||
Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb")
|
||||
});
|
||||
|
||||
var service = new SbomLedgerService(
|
||||
repository,
|
||||
clock,
|
||||
Options.Create(new SbomLedgerOptions()),
|
||||
lineageEdgeRepository: null,
|
||||
logger: NullLogger<SbomLedgerService>.Instance,
|
||||
guidProvider: guidProvider);
|
||||
|
||||
var submission = new SbomLedgerSubmission(
|
||||
ArtifactRef: "acme/app:1.0",
|
||||
Digest: "sha256:deadbeef",
|
||||
Format: "spdx",
|
||||
FormatVersion: "2.3",
|
||||
Source: "upload",
|
||||
Provenance: null,
|
||||
Components: new List<SbomNormalizedComponent>
|
||||
{
|
||||
new("pkg:npm/lodash", "lodash", "4.17.21", "pkg:npm/lodash@4.17.21", "MIT")
|
||||
},
|
||||
ParentVersionId: null);
|
||||
|
||||
// Act
|
||||
var version = await service.AddVersionAsync(submission, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
version.ChainId.Should().Be(Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"));
|
||||
version.VersionId.Should().Be(Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"));
|
||||
version.CreatedAtUtc.Should().Be(FixedNow);
|
||||
}
|
||||
|
||||
private sealed class FixedClock : IClock
|
||||
{
|
||||
public FixedClock(DateTimeOffset utcNow)
|
||||
{
|
||||
UtcNow = utcNow;
|
||||
}
|
||||
|
||||
public DateTimeOffset UtcNow { get; }
|
||||
}
|
||||
|
||||
private static DateTimeOffset FixedNow =>
|
||||
DateTimeOffset.Parse("2025-12-29T12:00:00Z", CultureInfo.InvariantCulture);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using StellaOps.SbomService.Models;
|
||||
using StellaOps.SbomService.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.SbomService.Tests;
|
||||
|
||||
public sealed class ScanJobEmitterServiceTests
|
||||
{
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task SubmitScanAsync_RejectsDisallowedScannerHost()
|
||||
{
|
||||
// Arrange
|
||||
var configuration = BuildConfiguration("http://blocked.example.com");
|
||||
var httpClientFactory = new Mock<IHttpClientFactory>();
|
||||
httpClientFactory
|
||||
.Setup(f => f.CreateClient(It.IsAny<string>()))
|
||||
.Returns(new HttpClient(new Mock<HttpMessageHandler>().Object));
|
||||
|
||||
var service = new ScanJobEmitterService(
|
||||
httpClientFactory.Object,
|
||||
configuration,
|
||||
NullLogger<ScanJobEmitterService>.Instance,
|
||||
Options.Create(new ScannerHttpOptions
|
||||
{
|
||||
AllowedHosts = new List<string> { "scanner.local" }
|
||||
}),
|
||||
new QueueGuidProvider(new[] { Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa") }));
|
||||
|
||||
var request = new ScanJobRequest(
|
||||
ImageReference: "library/nginx:latest",
|
||||
Digest: "sha256:deadbeef",
|
||||
Platform: "linux/amd64",
|
||||
Force: false,
|
||||
ClientRequestId: null,
|
||||
SourceId: "source-1",
|
||||
TriggerType: "manual");
|
||||
|
||||
// Act
|
||||
var result = await service.SubmitScanAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Success.Should().BeFalse();
|
||||
result.Error.Should().Contain("allowlisted");
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
[Fact]
|
||||
public async Task SubmitBatchScanAsync_UsesDeterministicRequestId()
|
||||
{
|
||||
// Arrange
|
||||
var configuration = BuildConfiguration("http://scanner.local");
|
||||
var handler = new Mock<HttpMessageHandler>();
|
||||
HttpRequestMessage? captured = null;
|
||||
|
||||
handler
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.IsAny<HttpRequestMessage>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<HttpRequestMessage, CancellationToken>((request, _) => captured = request)
|
||||
.ReturnsAsync(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = HttpStatusCode.OK,
|
||||
Content = new StringContent("{\"snapshot\":{\"id\":\"job-123\",\"status\":\"queued\"},\"created\":true}")
|
||||
});
|
||||
|
||||
var httpClientFactory = new Mock<IHttpClientFactory>();
|
||||
httpClientFactory
|
||||
.Setup(f => f.CreateClient(It.IsAny<string>()))
|
||||
.Returns(new HttpClient(handler.Object));
|
||||
|
||||
var service = new ScanJobEmitterService(
|
||||
httpClientFactory.Object,
|
||||
configuration,
|
||||
NullLogger<ScanJobEmitterService>.Instance,
|
||||
Options.Create(new ScannerHttpOptions
|
||||
{
|
||||
AllowedHosts = new List<string> { "scanner.local" }
|
||||
}),
|
||||
new QueueGuidProvider(new[] { Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb") }));
|
||||
|
||||
var images = new[]
|
||||
{
|
||||
new DiscoveredImage("library/nginx", "latest", "sha256:deadbeef")
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await service.SubmitBatchScanAsync("source-1", images);
|
||||
|
||||
// Assert
|
||||
result.Submitted.Should().Be(1);
|
||||
captured.Should().NotBeNull();
|
||||
|
||||
var payload = await captured!.Content!.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(payload);
|
||||
var clientRequestId = doc.RootElement.GetProperty("clientRequestId").GetString();
|
||||
clientRequestId.Should().Be("registry-source-1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
||||
}
|
||||
|
||||
private static IConfiguration BuildConfiguration(string scannerUrl)
|
||||
{
|
||||
return new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["SbomService:ScannerUrl"] = scannerUrl,
|
||||
["SbomService:BatchScanSize"] = "1",
|
||||
["SbomService:BatchScanDelayMs"] = "0"
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using StellaOps.SbomService.Services;
|
||||
|
||||
namespace StellaOps.SbomService.Tests;
|
||||
|
||||
internal sealed class FixedTimeProvider : TimeProvider
|
||||
{
|
||||
private DateTimeOffset _utcNow;
|
||||
private long _timestamp;
|
||||
|
||||
public FixedTimeProvider(DateTimeOffset utcNow)
|
||||
{
|
||||
_utcNow = utcNow;
|
||||
}
|
||||
|
||||
public void Advance(TimeSpan delta)
|
||||
{
|
||||
_utcNow = _utcNow.Add(delta);
|
||||
_timestamp += delta.Ticks;
|
||||
}
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _utcNow;
|
||||
|
||||
public override long GetTimestamp() => _timestamp;
|
||||
|
||||
public override long TimestampFrequency => TimeSpan.TicksPerSecond;
|
||||
}
|
||||
|
||||
internal sealed class QueueGuidProvider : IGuidProvider
|
||||
{
|
||||
private readonly Queue<Guid> _guids;
|
||||
|
||||
public QueueGuidProvider(IEnumerable<Guid> guids)
|
||||
{
|
||||
_guids = new Queue<Guid>(guids);
|
||||
}
|
||||
|
||||
public Guid NewGuid()
|
||||
{
|
||||
return _guids.Count > 0 ? _guids.Dequeue() : Guid.Empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user