up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -15,37 +15,37 @@ using StellaOps.Attestor.Infrastructure.Rekor;
using StellaOps.Attestor.Infrastructure.Storage;
using StellaOps.Attestor.Infrastructure.Submission;
using StellaOps.Attestor.Tests.Support;
using Xunit;
namespace StellaOps.Attestor.Tests;
public sealed class AttestorSubmissionServiceTests
{
[Fact]
public async Task SubmitAsync_ReturnsDeterministicUuid_OnDuplicateBundle()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions
{
Url = string.Empty
},
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.stellaops.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
using Xunit;
namespace StellaOps.Attestor.Tests;
public sealed class AttestorSubmissionServiceTests
{
[Fact]
public async Task SubmitAsync_ReturnsDeterministicUuid_OnDuplicateBundle()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions
{
Url = string.Empty
},
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.stellaops.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
var archiveStore = new NullAttestorArchiveStore(new NullLogger<NullAttestorArchiveStore>());
var auditSink = new InMemoryAttestorAuditSink();
@@ -66,21 +66,21 @@ public sealed class AttestorSubmissionServiceTests
logger,
TimeProvider.System,
metrics);
var request = CreateValidRequest(canonicalizer);
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default",
ClientCertificate = null,
MtlsThumbprint = "00"
};
var first = await service.SubmitAsync(request, context);
var second = await service.SubmitAsync(request, context);
var request = CreateValidRequest(canonicalizer);
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default",
ClientCertificate = null,
MtlsThumbprint = "00"
};
var first = await service.SubmitAsync(request, context);
var second = await service.SubmitAsync(request, context);
Assert.NotNull(first.Uuid);
Assert.Equal(first.Uuid, second.Uuid);
@@ -89,43 +89,43 @@ public sealed class AttestorSubmissionServiceTests
Assert.Equal(first.Uuid, stored!.RekorUuid);
Assert.Single(verificationCache.InvalidatedSubjects);
Assert.Equal(request.Meta.Artifact.Sha256, verificationCache.InvalidatedSubjects[0]);
}
[Fact]
public async Task Validator_ThrowsWhenModeNotAllowed()
{
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer, new[] { "kms" });
var request = CreateValidRequest(canonicalizer);
request.Bundle.Mode = "keyless";
await Assert.ThrowsAsync<AttestorValidationException>(() => validator.ValidateAsync(request));
}
[Fact]
public async Task SubmitAsync_Throws_WhenMirrorDisabledButRequested()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.primary.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
}
[Fact]
public async Task Validator_ThrowsWhenModeNotAllowed()
{
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer, new[] { "kms" });
var request = CreateValidRequest(canonicalizer);
request.Bundle.Mode = "keyless";
await Assert.ThrowsAsync<AttestorValidationException>(() => validator.ValidateAsync(request));
}
[Fact]
public async Task SubmitAsync_Throws_WhenMirrorDisabledButRequested()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.primary.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
var archiveStore = new NullAttestorArchiveStore(new NullLogger<NullAttestorArchiveStore>());
var auditSink = new InMemoryAttestorAuditSink();
var witnessClient = new TestTransparencyWitnessClient();
@@ -145,53 +145,53 @@ public sealed class AttestorSubmissionServiceTests
logger,
TimeProvider.System,
metrics);
var request = CreateValidRequest(canonicalizer);
request.Meta.LogPreference = "mirror";
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default"
};
var ex = await Assert.ThrowsAsync<AttestorValidationException>(() => service.SubmitAsync(request, context));
Assert.Equal("mirror_disabled", ex.Code);
}
[Fact]
public async Task SubmitAsync_ReturnsMirrorMetadata_WhenPreferenceBoth()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.primary.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
},
Mirror = new AttestorOptions.RekorMirrorOptions
{
Enabled = true,
Url = "https://rekor.mirror.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
var request = CreateValidRequest(canonicalizer);
request.Meta.LogPreference = "mirror";
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default"
};
var ex = await Assert.ThrowsAsync<AttestorValidationException>(() => service.SubmitAsync(request, context));
Assert.Equal("mirror_disabled", ex.Code);
}
[Fact]
public async Task SubmitAsync_ReturnsMirrorMetadata_WhenPreferenceBoth()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.primary.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
},
Mirror = new AttestorOptions.RekorMirrorOptions
{
Enabled = true,
Url = "https://rekor.mirror.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
var archiveStore = new NullAttestorArchiveStore(new NullLogger<NullAttestorArchiveStore>());
var auditSink = new InMemoryAttestorAuditSink();
var witnessClient = new TestTransparencyWitnessClient();
@@ -211,56 +211,56 @@ public sealed class AttestorSubmissionServiceTests
logger,
TimeProvider.System,
metrics);
var request = CreateValidRequest(canonicalizer);
request.Meta.LogPreference = "both";
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default"
};
var result = await service.SubmitAsync(request, context);
Assert.NotNull(result.Mirror);
Assert.False(string.IsNullOrEmpty(result.Mirror!.Uuid));
Assert.Equal("included", result.Mirror.Status);
}
[Fact]
public async Task SubmitAsync_UsesMirrorAsCanonical_WhenPreferenceMirror()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.primary.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
},
Mirror = new AttestorOptions.RekorMirrorOptions
{
Enabled = true,
Url = "https://rekor.mirror.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
var request = CreateValidRequest(canonicalizer);
request.Meta.LogPreference = "both";
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default"
};
var result = await service.SubmitAsync(request, context);
Assert.NotNull(result.Mirror);
Assert.False(string.IsNullOrEmpty(result.Mirror!.Uuid));
Assert.Equal("included", result.Mirror.Status);
}
[Fact]
public async Task SubmitAsync_UsesMirrorAsCanonical_WhenPreferenceMirror()
{
var options = Options.Create(new AttestorOptions
{
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
Rekor = new AttestorOptions.RekorOptions
{
Primary = new AttestorOptions.RekorBackendOptions
{
Url = "https://rekor.primary.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
},
Mirror = new AttestorOptions.RekorMirrorOptions
{
Enabled = true,
Url = "https://rekor.mirror.test",
ProofTimeoutMs = 1000,
PollIntervalMs = 50,
MaxAttempts = 2
}
}
});
var canonicalizer = new DefaultDsseCanonicalizer();
var validator = new AttestorSubmissionValidator(canonicalizer);
var repository = new InMemoryAttestorEntryRepository();
var dedupeStore = new InMemoryAttestorDedupeStore();
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
var archiveStore = new NullAttestorArchiveStore(new NullLogger<NullAttestorArchiveStore>());
var auditSink = new InMemoryAttestorAuditSink();
var witnessClient = new TestTransparencyWitnessClient();
@@ -280,24 +280,24 @@ public sealed class AttestorSubmissionServiceTests
logger,
TimeProvider.System,
metrics);
var request = CreateValidRequest(canonicalizer);
request.Meta.LogPreference = "mirror";
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default"
};
var result = await service.SubmitAsync(request, context);
Assert.NotNull(result.Uuid);
var stored = await repository.GetByBundleShaAsync(request.Meta.BundleSha256);
Assert.NotNull(stored);
Assert.Equal("mirror", stored!.Log.Backend);
var request = CreateValidRequest(canonicalizer);
request.Meta.LogPreference = "mirror";
var context = new SubmissionContext
{
CallerSubject = "urn:stellaops:signer",
CallerAudience = "attestor",
CallerClientId = "signer-service",
CallerTenant = "default"
};
var result = await service.SubmitAsync(request, context);
Assert.NotNull(result.Uuid);
var stored = await repository.GetByBundleShaAsync(request.Meta.BundleSha256);
Assert.NotNull(stored);
Assert.Equal("mirror", stored!.Log.Backend);
Assert.Null(result.Mirror);
}
@@ -323,36 +323,36 @@ public sealed class AttestorSubmissionServiceTests
var request = new AttestorSubmissionRequest
{
Bundle = new AttestorSubmissionRequest.SubmissionBundle
{
Mode = "keyless",
Dsse = new AttestorSubmissionRequest.DsseEnvelope
{
PayloadType = "application/vnd.in-toto+json",
PayloadBase64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("{}")),
Signatures =
{
new AttestorSubmissionRequest.DsseSignature
{
KeyId = "test",
Signature = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
}
}
}
},
Meta = new AttestorSubmissionRequest.SubmissionMeta
{
Artifact = new AttestorSubmissionRequest.ArtifactInfo
{
Sha256 = new string('a', 64),
Kind = "sbom"
},
LogPreference = "primary",
Archive = false
}
};
var canonical = canonicalizer.CanonicalizeAsync(request).GetAwaiter().GetResult();
request.Meta.BundleSha256 = Convert.ToHexString(SHA256.HashData(canonical)).ToLowerInvariant();
return request;
}
}
{
Mode = "keyless",
Dsse = new AttestorSubmissionRequest.DsseEnvelope
{
PayloadType = "application/vnd.in-toto+json",
PayloadBase64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("{}")),
Signatures =
{
new AttestorSubmissionRequest.DsseSignature
{
KeyId = "test",
Signature = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
}
}
}
},
Meta = new AttestorSubmissionRequest.SubmissionMeta
{
Artifact = new AttestorSubmissionRequest.ArtifactInfo
{
Sha256 = new string('a', 64),
Kind = "sbom"
},
LogPreference = "primary",
Archive = false
}
};
var canonical = canonicalizer.CanonicalizeAsync(request).GetAwaiter().GetResult();
request.Meta.BundleSha256 = Convert.ToHexString(SHA256.HashData(canonical)).ToLowerInvariant();
return request;
}
}

View File

@@ -1,149 +1,149 @@
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Attestor.Core.Rekor;
using StellaOps.Attestor.Core.Submission;
using StellaOps.Attestor.Infrastructure.Rekor;
using Xunit;
namespace StellaOps.Attestor.Tests;
public sealed class HttpRekorClientTests
{
[Fact]
public async Task SubmitAsync_ParsesResponse()
{
var payload = new
{
uuid = "123",
index = 42,
logURL = "https://rekor.example/api/v2/log/entries/123",
status = "included",
proof = new
{
checkpoint = new { origin = "rekor", size = 10, rootHash = "abc", timestamp = "2025-10-19T00:00:00Z" },
inclusion = new { leafHash = "leaf", path = new[] { "p1", "p2" } }
}
};
var client = CreateClient(HttpStatusCode.Created, payload);
var rekorClient = new HttpRekorClient(client, NullLogger<HttpRekorClient>.Instance);
var request = new AttestorSubmissionRequest
{
Bundle = new AttestorSubmissionRequest.SubmissionBundle
{
Dsse = new AttestorSubmissionRequest.DsseEnvelope
{
PayloadType = "application/json",
PayloadBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{}")),
Signatures = { new AttestorSubmissionRequest.DsseSignature { Signature = "sig" } }
}
}
};
var backend = new RekorBackend
{
Name = "primary",
Url = new Uri("https://rekor.example/"),
ProofTimeout = TimeSpan.FromSeconds(1),
PollInterval = TimeSpan.FromMilliseconds(100),
MaxAttempts = 1
};
var response = await rekorClient.SubmitAsync(request, backend);
Assert.Equal("123", response.Uuid);
Assert.Equal(42, response.Index);
Assert.Equal("included", response.Status);
Assert.NotNull(response.Proof);
Assert.Equal("leaf", response.Proof!.Inclusion!.LeafHash);
}
[Fact]
public async Task SubmitAsync_ThrowsOnConflict()
{
var client = CreateClient(HttpStatusCode.Conflict, new { error = "duplicate" });
var rekorClient = new HttpRekorClient(client, NullLogger<HttpRekorClient>.Instance);
var request = new AttestorSubmissionRequest
{
Bundle = new AttestorSubmissionRequest.SubmissionBundle
{
Dsse = new AttestorSubmissionRequest.DsseEnvelope
{
PayloadType = "application/json",
PayloadBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{}")),
Signatures = { new AttestorSubmissionRequest.DsseSignature { Signature = "sig" } }
}
}
};
var backend = new RekorBackend
{
Name = "primary",
Url = new Uri("https://rekor.example/"),
ProofTimeout = TimeSpan.FromSeconds(1),
PollInterval = TimeSpan.FromMilliseconds(100),
MaxAttempts = 1
};
await Assert.ThrowsAsync<InvalidOperationException>(() => rekorClient.SubmitAsync(request, backend));
}
[Fact]
public async Task GetProofAsync_ReturnsNullOnNotFound()
{
var client = CreateClient(HttpStatusCode.NotFound, new { });
var rekorClient = new HttpRekorClient(client, NullLogger<HttpRekorClient>.Instance);
var backend = new RekorBackend
{
Name = "primary",
Url = new Uri("https://rekor.example/"),
ProofTimeout = TimeSpan.FromSeconds(1),
PollInterval = TimeSpan.FromMilliseconds(100),
MaxAttempts = 1
};
var proof = await rekorClient.GetProofAsync("abc", backend);
Assert.Null(proof);
}
private static HttpClient CreateClient(HttpStatusCode statusCode, object payload)
{
var handler = new StubHandler(statusCode, payload);
return new HttpClient(handler)
{
BaseAddress = new Uri("https://rekor.example/")
};
}
private sealed class StubHandler : HttpMessageHandler
{
private readonly HttpStatusCode _statusCode;
private readonly object _payload;
public StubHandler(HttpStatusCode statusCode, object payload)
{
_statusCode = statusCode;
_payload = payload;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var json = JsonSerializer.Serialize(_payload);
var response = new HttpResponseMessage(_statusCode)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
return Task.FromResult(response);
}
}
}
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Attestor.Core.Rekor;
using StellaOps.Attestor.Core.Submission;
using StellaOps.Attestor.Infrastructure.Rekor;
using Xunit;
namespace StellaOps.Attestor.Tests;
public sealed class HttpRekorClientTests
{
[Fact]
public async Task SubmitAsync_ParsesResponse()
{
var payload = new
{
uuid = "123",
index = 42,
logURL = "https://rekor.example/api/v2/log/entries/123",
status = "included",
proof = new
{
checkpoint = new { origin = "rekor", size = 10, rootHash = "abc", timestamp = "2025-10-19T00:00:00Z" },
inclusion = new { leafHash = "leaf", path = new[] { "p1", "p2" } }
}
};
var client = CreateClient(HttpStatusCode.Created, payload);
var rekorClient = new HttpRekorClient(client, NullLogger<HttpRekorClient>.Instance);
var request = new AttestorSubmissionRequest
{
Bundle = new AttestorSubmissionRequest.SubmissionBundle
{
Dsse = new AttestorSubmissionRequest.DsseEnvelope
{
PayloadType = "application/json",
PayloadBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{}")),
Signatures = { new AttestorSubmissionRequest.DsseSignature { Signature = "sig" } }
}
}
};
var backend = new RekorBackend
{
Name = "primary",
Url = new Uri("https://rekor.example/"),
ProofTimeout = TimeSpan.FromSeconds(1),
PollInterval = TimeSpan.FromMilliseconds(100),
MaxAttempts = 1
};
var response = await rekorClient.SubmitAsync(request, backend);
Assert.Equal("123", response.Uuid);
Assert.Equal(42, response.Index);
Assert.Equal("included", response.Status);
Assert.NotNull(response.Proof);
Assert.Equal("leaf", response.Proof!.Inclusion!.LeafHash);
}
[Fact]
public async Task SubmitAsync_ThrowsOnConflict()
{
var client = CreateClient(HttpStatusCode.Conflict, new { error = "duplicate" });
var rekorClient = new HttpRekorClient(client, NullLogger<HttpRekorClient>.Instance);
var request = new AttestorSubmissionRequest
{
Bundle = new AttestorSubmissionRequest.SubmissionBundle
{
Dsse = new AttestorSubmissionRequest.DsseEnvelope
{
PayloadType = "application/json",
PayloadBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{}")),
Signatures = { new AttestorSubmissionRequest.DsseSignature { Signature = "sig" } }
}
}
};
var backend = new RekorBackend
{
Name = "primary",
Url = new Uri("https://rekor.example/"),
ProofTimeout = TimeSpan.FromSeconds(1),
PollInterval = TimeSpan.FromMilliseconds(100),
MaxAttempts = 1
};
await Assert.ThrowsAsync<InvalidOperationException>(() => rekorClient.SubmitAsync(request, backend));
}
[Fact]
public async Task GetProofAsync_ReturnsNullOnNotFound()
{
var client = CreateClient(HttpStatusCode.NotFound, new { });
var rekorClient = new HttpRekorClient(client, NullLogger<HttpRekorClient>.Instance);
var backend = new RekorBackend
{
Name = "primary",
Url = new Uri("https://rekor.example/"),
ProofTimeout = TimeSpan.FromSeconds(1),
PollInterval = TimeSpan.FromMilliseconds(100),
MaxAttempts = 1
};
var proof = await rekorClient.GetProofAsync("abc", backend);
Assert.Null(proof);
}
private static HttpClient CreateClient(HttpStatusCode statusCode, object payload)
{
var handler = new StubHandler(statusCode, payload);
return new HttpClient(handler)
{
BaseAddress = new Uri("https://rekor.example/")
};
}
private sealed class StubHandler : HttpMessageHandler
{
private readonly HttpStatusCode _statusCode;
private readonly object _payload;
public StubHandler(HttpStatusCode statusCode, object payload)
{
_statusCode = statusCode;
_payload = payload;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var json = JsonSerializer.Serialize(_payload);
var response = new HttpResponseMessage(_statusCode)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
return Task.FromResult(response);
}
}
}