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:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
@@ -17,9 +18,10 @@ using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.Client;
|
||||
using StellaOps.Cli.Configuration;
|
||||
using StellaOps.Cli.Services;
|
||||
using StellaOps.Cli.Services.Models;
|
||||
using StellaOps.Cli.Services.Models.Transport;
|
||||
using StellaOps.Cli.Tests.Testing;
|
||||
using StellaOps.Cli.Services.Models;
|
||||
using StellaOps.Cli.Services.Models.Transport;
|
||||
using StellaOps.Cli.Tests.Testing;
|
||||
using StellaOps.Scanner.EntryTrace;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Services;
|
||||
@@ -170,11 +172,11 @@ public sealed class BackendOperationsClientTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UploadScanResultsAsync_RetriesOnRetryAfter()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var filePath = Path.Combine(temp.Path, "scan.json");
|
||||
await File.WriteAllTextAsync(filePath, "{}");
|
||||
public async Task UploadScanResultsAsync_RetriesOnRetryAfter()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var filePath = Path.Combine(temp.Path, "scan.json");
|
||||
await File.WriteAllTextAsync(filePath, "{}");
|
||||
|
||||
var attempts = 0;
|
||||
var handler = new StubHttpMessageHandler(
|
||||
@@ -250,9 +252,103 @@ public sealed class BackendOperationsClientTests
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => client.UploadScanResultsAsync(filePath, CancellationToken.None));
|
||||
Assert.Equal(2, attempts);
|
||||
}
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => client.UploadScanResultsAsync(filePath, CancellationToken.None));
|
||||
Assert.Equal(2, attempts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEntryTraceAsync_ReturnsResponse()
|
||||
{
|
||||
var scanId = $"scan-{Guid.NewGuid():n}";
|
||||
var generatedAt = new DateTimeOffset(2025, 11, 1, 8, 30, 0, TimeSpan.Zero);
|
||||
var plan = new EntryTracePlan(
|
||||
ImmutableArray.Create("/usr/bin/app"),
|
||||
ImmutableDictionary<string, string>.Empty,
|
||||
"/work",
|
||||
"root",
|
||||
"/usr/bin/app",
|
||||
EntryTraceTerminalType.Native,
|
||||
"go",
|
||||
80d,
|
||||
ImmutableDictionary<string, string>.Empty);
|
||||
var terminal = new EntryTraceTerminal(
|
||||
"/usr/bin/app",
|
||||
EntryTraceTerminalType.Native,
|
||||
"go",
|
||||
80d,
|
||||
ImmutableDictionary<string, string>.Empty,
|
||||
"root",
|
||||
"/work",
|
||||
ImmutableArray<string>.Empty);
|
||||
var graph = new EntryTraceGraph(
|
||||
EntryTraceOutcome.Resolved,
|
||||
ImmutableArray<EntryTraceNode>.Empty,
|
||||
ImmutableArray<EntryTraceEdge>.Empty,
|
||||
ImmutableArray<EntryTraceDiagnostic>.Empty,
|
||||
ImmutableArray.Create(plan),
|
||||
ImmutableArray.Create(terminal));
|
||||
var responseModel = new EntryTraceResponseModel(
|
||||
scanId,
|
||||
"sha256:test",
|
||||
generatedAt,
|
||||
graph,
|
||||
EntryTraceNdjsonWriter.Serialize(graph, new EntryTraceNdjsonMetadata(scanId, "sha256:test", generatedAt)));
|
||||
var json = JsonSerializer.Serialize(responseModel, new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||
|
||||
var handler = new StubHttpMessageHandler((request, _) =>
|
||||
{
|
||||
var message = new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
RequestMessage = request,
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
return message;
|
||||
});
|
||||
|
||||
var httpClient = new HttpClient(handler)
|
||||
{
|
||||
BaseAddress = new Uri("https://scanner.example")
|
||||
};
|
||||
|
||||
var options = new StellaOpsCliOptions
|
||||
{
|
||||
BackendUrl = "https://scanner.example"
|
||||
};
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
|
||||
var result = await client.GetEntryTraceAsync(scanId, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(responseModel.ScanId, result!.ScanId);
|
||||
Assert.Equal(responseModel.ImageDigest, result.ImageDigest);
|
||||
Assert.Equal(responseModel.Graph.Plans.Length, result.Graph.Plans.Length);
|
||||
Assert.Equal(responseModel.Ndjson.Count, result.Ndjson.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEntryTraceAsync_ReturnsNullWhenNotFound()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler((request, _) => new HttpResponseMessage(HttpStatusCode.NotFound)
|
||||
{
|
||||
RequestMessage = request
|
||||
});
|
||||
|
||||
var httpClient = new HttpClient(handler)
|
||||
{
|
||||
BaseAddress = new Uri("https://scanner.example")
|
||||
};
|
||||
|
||||
var options = new StellaOpsCliOptions
|
||||
{
|
||||
BackendUrl = "https://scanner.example"
|
||||
};
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
|
||||
var result = await client.GetEntryTraceAsync("scan-missing", CancellationToken.None);
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TriggerJobAsync_ReturnsAcceptedResult()
|
||||
@@ -809,13 +905,13 @@ public sealed class BackendOperationsClientTests
|
||||
switch (name)
|
||||
{
|
||||
case "metadata":
|
||||
MetadataJson = await part.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
MetadataJson = await part.ReadAsStringAsync(cancellationToken);
|
||||
break;
|
||||
case "bundle":
|
||||
BundlePayload = await part.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||
BundlePayload = await part.ReadAsByteArrayAsync(cancellationToken);
|
||||
break;
|
||||
case "manifest":
|
||||
ManifestPayload = await part.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||
ManifestPayload = await part.ReadAsByteArrayAsync(cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user