using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using StellaOps.Cryptography; using StellaOps.EvidenceLocker.Core.Configuration; using StellaOps.EvidenceLocker.Core.Signing; using StellaOps.EvidenceLocker.Infrastructure.Signing; using StellaOps.TestKit; using System; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Xunit; namespace StellaOps.EvidenceLocker.Tests; public sealed class Rfc3161TimestampAuthorityClientTests { [Trait("Category", TestCategories.Unit)] [Fact] public async Task RequestTimestampAsync_ReturnsNull_WhenAuthorityFailsAndTimestampOptional() { var handler = new StubHttpMessageHandler(_ => new HttpResponseMessage(HttpStatusCode.InternalServerError)); var client = CreateClient(handler, new TimestampingOptions { Enabled = true, Endpoint = "https://tsa.example", HashAlgorithm = "SHA256", RequireTimestamp = false }); var result = await client.RequestTimestampAsync(new byte[] { 4, 5, 6 }, "SHA256", CancellationToken.None); Assert.Null(result); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task RequestTimestampAsync_Throws_WhenAuthorityFailsAndTimestampRequired() { var handler = new StubHttpMessageHandler(_ => new HttpResponseMessage(HttpStatusCode.InternalServerError)); var client = CreateClient(handler, new TimestampingOptions { Enabled = true, Endpoint = "https://tsa.example", HashAlgorithm = "SHA256", RequireTimestamp = true }); await Assert.ThrowsAsync(() => client.RequestTimestampAsync(new byte[] { 7, 8 }, "SHA256", CancellationToken.None)); } private static Rfc3161TimestampAuthorityClient CreateClient(HttpMessageHandler handler, TimestampingOptions timestampingOptions) { var httpClient = new HttpClient(handler, disposeHandler: false); var options = Options.Create(new EvidenceLockerOptions { Database = new DatabaseOptions { ConnectionString = "Host=localhost" }, ObjectStore = new ObjectStoreOptions { Kind = ObjectStoreKind.FileSystem, FileSystem = new FileSystemStoreOptions { RootPath = "." } }, Quotas = new QuotaOptions(), Signing = new SigningOptions { Algorithm = SignatureAlgorithms.Es256, KeyId = "test-key", Timestamping = timestampingOptions } }); return new Rfc3161TimestampAuthorityClient(httpClient, options, NullLogger.Instance); } private sealed class StubHttpMessageHandler(Func responder) : HttpMessageHandler { private readonly Func _responder = responder; protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => Task.FromResult(_responder(request)); } }