Add Authority Advisory AI and API Lifecycle Configuration
- Introduced AuthorityAdvisoryAiOptions and related classes for managing advisory AI configurations, including remote inference options and tenant-specific settings. - Added AuthorityApiLifecycleOptions to control API lifecycle settings, including legacy OAuth endpoint configurations. - Implemented validation and normalization methods for both advisory AI and API lifecycle options to ensure proper configuration. - Created AuthorityNotificationsOptions and its related classes for managing notification settings, including ack tokens, webhooks, and escalation options. - Developed IssuerDirectoryClient and related models for interacting with the issuer directory service, including caching mechanisms and HTTP client configurations. - Added support for dependency injection through ServiceCollectionExtensions for the Issuer Directory Client. - Updated project file to include necessary package references for the new Issuer Directory Client library.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Types.Tests;
|
||||
|
||||
public class AttestationGoldenSamplesTests
|
||||
{
|
||||
private const string ExpectedSubjectDigest = "d5f5e54d1e1a4c3c7b18961ea7cadb88ec0a93a9f2f40f0e823d9184c83e4d72";
|
||||
|
||||
[Fact]
|
||||
public void EverySampleIsCanonicalAndComplete()
|
||||
{
|
||||
var samplesDirectory = Path.Combine(AppContext.BaseDirectory, "samples");
|
||||
Directory.Exists(samplesDirectory)
|
||||
.Should()
|
||||
.BeTrue($"golden samples should be copied to '{samplesDirectory}'");
|
||||
|
||||
var sampleFiles = Directory.EnumerateFiles(samplesDirectory, "*.json", SearchOption.TopDirectoryOnly)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
sampleFiles.Should().NotBeEmpty("golden attestation samples must exist");
|
||||
|
||||
foreach (var samplePath in sampleFiles)
|
||||
{
|
||||
var json = File.ReadAllText(samplePath);
|
||||
var node = JsonNode.Parse(json, new JsonNodeOptions { PropertyNameCaseInsensitive = false })
|
||||
?? throw new InvalidOperationException($"Failed to parse sample '{samplePath}'.");
|
||||
|
||||
node.Should().BeOfType<JsonObject>($"sample '{samplePath}' must be a JSON object");
|
||||
AssertObjectKeysSorted(node.AsObject(), Path.GetFileName(samplePath));
|
||||
|
||||
node["_type"]
|
||||
.Should()
|
||||
.NotBeNull($"sample '{samplePath}' must declare in-toto type")
|
||||
.And
|
||||
.Subject.As<JsonValue>()
|
||||
.GetValue<string>()
|
||||
.Should()
|
||||
.Be("https://in-toto.io/Statement/v1");
|
||||
|
||||
var predicateType = node["predicateType"]?.GetValue<string>();
|
||||
predicateType.Should().NotBeNullOrWhiteSpace($"sample '{samplePath}' must declare predicateType");
|
||||
predicateType!.Should().MatchRegex(@"^StellaOps\.[A-Za-z]+@1$", "predicate types follow naming convention");
|
||||
|
||||
node["predicateVersion"]
|
||||
?.GetValue<string>()
|
||||
.Should()
|
||||
.Be("1.0.0", $"sample '{samplePath}' must lock predicateVersion");
|
||||
|
||||
var subjectArray = node["subject"]?.AsArray();
|
||||
subjectArray.Should().NotBeNullOrEmpty($"sample '{samplePath}' must describe subject digests");
|
||||
|
||||
for (var index = 0; index < subjectArray!.Count; index++)
|
||||
{
|
||||
var subjectEntry = subjectArray[index] ?? throw new InvalidOperationException($"Null subject entry at index {index} in '{samplePath}'.");
|
||||
|
||||
var digest = subjectEntry["digest"]?["sha256"]?.GetValue<string>();
|
||||
digest.Should().NotBeNullOrWhiteSpace($"sample '{samplePath}' requires subject.digest.sha256");
|
||||
digest!.Should().Be(ExpectedSubjectDigest, "golden samples share a single canonical digest");
|
||||
|
||||
var name = subjectEntry["name"]?.GetValue<string>();
|
||||
name.Should().NotBeNullOrWhiteSpace($"sample '{samplePath}' requires subject name");
|
||||
name!.Should().Contain(ExpectedSubjectDigest, "subject name should embed the digest");
|
||||
|
||||
AssertObjectKeysSorted(subjectEntry.AsObject(), $"{Path.GetFileName(samplePath)}:subject[{index}]");
|
||||
AssertCanonicalRecursively(subjectEntry, $"{Path.GetFileName(samplePath)}:subject[{index}]");
|
||||
}
|
||||
|
||||
var predicate = node["predicate"]?.AsObject();
|
||||
predicate.Should().NotBeNull($"sample '{samplePath}' must include predicate content");
|
||||
|
||||
AssertObjectKeysSorted(predicate!, $"{Path.GetFileName(samplePath)}:predicate");
|
||||
AssertCanonicalRecursively(predicate!, $"{Path.GetFileName(samplePath)}:predicate");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertCanonicalRecursively(JsonNode node, string path)
|
||||
{
|
||||
switch (node)
|
||||
{
|
||||
case JsonObject obj:
|
||||
AssertObjectKeysSorted(obj, path);
|
||||
|
||||
foreach (var property in obj)
|
||||
{
|
||||
property.Value.Should().NotBeNull($"property '{path}.{property.Key}' must not be null");
|
||||
AssertCanonicalRecursively(property.Value!, $"{path}.{property.Key}");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case JsonArray array:
|
||||
for (var index = 0; index < array.Count; index++)
|
||||
{
|
||||
var element = array[index];
|
||||
element.Should().NotBeNull($"array element '{path}[{index}]' must not be null");
|
||||
AssertCanonicalRecursively(element!, $"{path}[{index}]");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertObjectKeysSorted(JsonObject obj, string path)
|
||||
{
|
||||
string? previous = null;
|
||||
|
||||
foreach (var property in obj)
|
||||
{
|
||||
if (previous is not null)
|
||||
{
|
||||
string.CompareOrdinal(previous, property.Key)
|
||||
.Should()
|
||||
.BeLessOrEqualTo(0, $"object '{path}' must keep keys in lexicographical order");
|
||||
}
|
||||
|
||||
previous = property.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\StellaOps.Attestor.Types\samples\**\*.json" LinkBase="samples" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user