Add tests and implement StubBearer authentication for Signer endpoints
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Created SignerEndpointsTests to validate the SignDsse and VerifyReferrers endpoints.
- Implemented StubBearerAuthenticationDefaults and StubBearerAuthenticationHandler for token-based authentication.
- Developed ConcelierExporterClient for managing Trivy DB settings and export operations.
- Added TrivyDbSettingsPageComponent for UI interactions with Trivy DB settings, including form handling and export triggering.
- Implemented styles and HTML structure for Trivy DB settings page.
- Created NotifySmokeCheck tool for validating Redis event streams and Notify deliveries.
This commit is contained in:
2025-10-21 09:37:07 +03:00
parent 2b6304c9c3
commit 791e12baab
298 changed files with 20490 additions and 5751 deletions

View File

@@ -951,6 +951,15 @@ public sealed class CommandHandlersTests
public Task<RuntimePolicyEvaluationResult> EvaluateRuntimePolicyAsync(RuntimePolicyEvaluationRequest request, CancellationToken cancellationToken)
=> Task.FromResult(RuntimePolicyResult);
public Task<OfflineKitDownloadResult> DownloadOfflineKitAsync(string? bundleId, string destinationDirectory, bool overwrite, bool resume, CancellationToken cancellationToken)
=> throw new NotSupportedException();
public Task<OfflineKitImportResult> ImportOfflineKitAsync(OfflineKitImportRequest request, CancellationToken cancellationToken)
=> throw new NotSupportedException();
public Task<OfflineKitStatus> GetOfflineKitStatusAsync(CancellationToken cancellationToken)
=> throw new NotSupportedException();
}
private sealed class StubExecutor : IScannerExecutor

View File

@@ -1,24 +1,25 @@
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
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 System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
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 System.Linq;
namespace StellaOps.Cli.Tests.Services;
@@ -481,7 +482,352 @@ public sealed class BackendOperationsClientTests
Assert.Equal("manual-override", Assert.IsType<string>(secondary.AdditionalProperties["quietedBy"]));
}
private sealed class StubTokenClient : IStellaOpsTokenClient
[Fact]
public async Task DownloadOfflineKitAsync_DownloadsBundleAndWritesMetadata()
{
using var temp = new TempDirectory();
var bundleBytes = Encoding.UTF8.GetBytes("bundle-data");
var manifestBytes = Encoding.UTF8.GetBytes("{\"artifacts\":[]}");
var bundleDigest = Convert.ToHexString(SHA256.HashData(bundleBytes)).ToLowerInvariant();
var manifestDigest = Convert.ToHexString(SHA256.HashData(manifestBytes)).ToLowerInvariant();
var metadataPayload = JsonSerializer.Serialize(new
{
bundleId = "2025-10-20-full",
bundleName = "stella-ops-offline-kit-2025-10-20.tgz",
bundleSha256 = $"sha256:{bundleDigest}",
bundleSize = (long)bundleBytes.Length,
bundleUrl = "https://mirror.example/stella-ops-offline-kit-2025-10-20.tgz",
bundleSignatureName = "stella-ops-offline-kit-2025-10-20.tgz.sig",
bundleSignatureUrl = "https://mirror.example/stella-ops-offline-kit-2025-10-20.tgz.sig",
manifestName = "offline-manifest-2025-10-20.json",
manifestSha256 = $"sha256:{manifestDigest}",
manifestUrl = "https://mirror.example/offline-manifest-2025-10-20.json",
manifestSignatureName = "offline-manifest-2025-10-20.json.jws",
manifestSignatureUrl = "https://mirror.example/offline-manifest-2025-10-20.json.jws",
capturedAt = DateTimeOffset.UtcNow
}, new JsonSerializerOptions(JsonSerializerDefaults.Web));
var handler = new StubHttpMessageHandler(
(request, _) =>
{
Assert.Equal("https://backend.example/api/offline-kit/bundles/latest", request.RequestUri!.ToString());
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(metadataPayload)
};
},
(request, _) =>
{
var absolute = request.RequestUri!.AbsoluteUri;
if (absolute.EndsWith(".tgz", StringComparison.OrdinalIgnoreCase))
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(bundleBytes)
};
}
if (absolute.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(manifestBytes)
};
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(Array.Empty<byte>())
};
});
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri("https://backend.example")
};
var options = new StellaOpsCliOptions
{
BackendUrl = "https://backend.example",
Offline = new StellaOpsCliOfflineOptions
{
KitsDirectory = temp.Path
}
};
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
var result = await client.DownloadOfflineKitAsync(null, temp.Path, overwrite: false, resume: false, CancellationToken.None);
Assert.False(result.FromCache);
Assert.True(File.Exists(result.BundlePath));
Assert.True(File.Exists(result.ManifestPath));
Assert.NotNull(result.BundleSignaturePath);
Assert.NotNull(result.ManifestSignaturePath);
Assert.True(File.Exists(result.MetadataPath));
using var metadata = JsonDocument.Parse(File.ReadAllText(result.MetadataPath));
Assert.Equal("2025-10-20-full", metadata.RootElement.GetProperty("bundleId").GetString());
Assert.Equal(bundleDigest, metadata.RootElement.GetProperty("bundleSha256").GetString());
}
[Fact]
public async Task DownloadOfflineKitAsync_ResumesPartialDownload()
{
using var temp = new TempDirectory();
var bundleBytes = Encoding.UTF8.GetBytes("partial-download-data");
var manifestBytes = Encoding.UTF8.GetBytes("{\"manifest\":true}");
var bundleDigest = Convert.ToHexString(SHA256.HashData(bundleBytes)).ToLowerInvariant();
var manifestDigest = Convert.ToHexString(SHA256.HashData(manifestBytes)).ToLowerInvariant();
var metadataJson = JsonSerializer.Serialize(new
{
bundleId = "2025-10-21-full",
bundleName = "kit.tgz",
bundleSha256 = bundleDigest,
bundleSize = (long)bundleBytes.Length,
bundleUrl = "https://mirror.example/kit.tgz",
manifestName = "offline-manifest.json",
manifestSha256 = manifestDigest,
manifestUrl = "https://mirror.example/offline-manifest.json",
capturedAt = DateTimeOffset.UtcNow
}, new JsonSerializerOptions(JsonSerializerDefaults.Web));
var partialPath = Path.Combine(temp.Path, "kit.tgz.partial");
await File.WriteAllBytesAsync(partialPath, bundleBytes.AsSpan(0, bundleBytes.Length / 2).ToArray());
var handler = new StubHttpMessageHandler(
(request, _) => new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(metadataJson)
},
(request, _) =>
{
if (request.RequestUri!.AbsoluteUri.EndsWith("kit.tgz", StringComparison.OrdinalIgnoreCase))
{
Assert.NotNull(request.Headers.Range);
Assert.Equal(bundleBytes.Length / 2, request.Headers.Range!.Ranges.Single().From);
return new HttpResponseMessage(HttpStatusCode.PartialContent)
{
Content = new ByteArrayContent(bundleBytes.AsSpan(bundleBytes.Length / 2).ToArray())
};
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(manifestBytes)
};
});
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri("https://backend.example")
};
var options = new StellaOpsCliOptions
{
BackendUrl = "https://backend.example",
Offline = new StellaOpsCliOfflineOptions
{
KitsDirectory = temp.Path
}
};
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
var result = await client.DownloadOfflineKitAsync(null, temp.Path, overwrite: false, resume: true, CancellationToken.None);
Assert.Equal(bundleDigest, result.Descriptor.BundleSha256);
Assert.Equal(bundleBytes.Length, new FileInfo(result.BundlePath).Length);
}
[Fact]
public async Task ImportOfflineKitAsync_SendsMultipartPayload()
{
using var temp = new TempDirectory();
var bundlePath = Path.Combine(temp.Path, "kit.tgz");
var manifestPath = Path.Combine(temp.Path, "offline-manifest.json");
var bundleBytes = Encoding.UTF8.GetBytes("bundle-content");
var manifestBytes = Encoding.UTF8.GetBytes("{\"manifest\":true}");
await File.WriteAllBytesAsync(bundlePath, bundleBytes);
await File.WriteAllBytesAsync(manifestPath, manifestBytes);
var bundleDigest = Convert.ToHexString(SHA256.HashData(bundleBytes)).ToLowerInvariant();
var manifestDigest = Convert.ToHexString(SHA256.HashData(manifestBytes)).ToLowerInvariant();
var metadata = new OfflineKitMetadataDocument
{
BundleId = "2025-10-21-full",
BundleName = "kit.tgz",
BundleSha256 = bundleDigest,
BundleSize = bundleBytes.Length,
BundlePath = bundlePath,
CapturedAt = DateTimeOffset.UtcNow,
DownloadedAt = DateTimeOffset.UtcNow,
Channel = "stable",
Kind = "full",
ManifestName = "offline-manifest.json",
ManifestSha256 = manifestDigest,
ManifestSize = manifestBytes.Length,
ManifestPath = manifestPath,
IsDelta = false,
BaseBundleId = null
};
await File.WriteAllTextAsync(bundlePath + ".metadata.json", JsonSerializer.Serialize(metadata, new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true }));
var recordingHandler = new ImportRecordingHandler();
var httpClient = new HttpClient(recordingHandler)
{
BaseAddress = new Uri("https://backend.example")
};
var options = new StellaOpsCliOptions
{
BackendUrl = "https://backend.example"
};
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
var request = new OfflineKitImportRequest(
bundlePath,
manifestPath,
null,
null,
metadata.BundleId,
metadata.BundleSha256,
metadata.BundleSize,
metadata.CapturedAt,
metadata.Channel,
metadata.Kind,
metadata.IsDelta,
metadata.BaseBundleId,
metadata.ManifestSha256,
metadata.ManifestSize);
var result = await client.ImportOfflineKitAsync(request, CancellationToken.None);
Assert.Equal("imp-1", result.ImportId);
Assert.NotNull(recordingHandler.MetadataJson);
Assert.NotNull(recordingHandler.BundlePayload);
Assert.NotNull(recordingHandler.ManifestPayload);
using var metadataJson = JsonDocument.Parse(recordingHandler.MetadataJson!);
Assert.Equal(bundleDigest, metadataJson.RootElement.GetProperty("bundleSha256").GetString());
Assert.Equal(manifestDigest, metadataJson.RootElement.GetProperty("manifestSha256").GetString());
}
[Fact]
public async Task GetOfflineKitStatusAsync_ParsesResponse()
{
var captured = DateTimeOffset.UtcNow;
var imported = captured.AddMinutes(5);
var statusJson = JsonSerializer.Serialize(new
{
current = new
{
bundleId = "2025-10-22-full",
channel = "stable",
kind = "full",
isDelta = false,
baseBundleId = (string?)null,
bundleSha256 = "sha256:abc123",
bundleSize = 42,
capturedAt = captured,
importedAt = imported
},
components = new[]
{
new
{
name = "concelier-json",
version = "2025-10-22",
digest = "sha256:def456",
capturedAt = captured,
sizeBytes = 1234
}
}
}, new JsonSerializerOptions(JsonSerializerDefaults.Web));
var handler = new StubHttpMessageHandler(
(request, _) =>
{
Assert.Equal("https://backend.example/api/offline-kit/status", request.RequestUri!.ToString());
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(statusJson)
};
});
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri("https://backend.example")
};
var options = new StellaOpsCliOptions
{
BackendUrl = "https://backend.example"
};
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
var status = await client.GetOfflineKitStatusAsync(CancellationToken.None);
Assert.Equal("2025-10-22-full", status.BundleId);
Assert.Equal("stable", status.Channel);
Assert.Equal("full", status.Kind);
Assert.False(status.IsDelta);
Assert.Equal(42, status.BundleSize);
Assert.Single(status.Components);
Assert.Equal("concelier-json", status.Components[0].Name);
}
private sealed class ImportRecordingHandler : HttpMessageHandler
{
public string? MetadataJson { get; private set; }
public byte[]? BundlePayload { get; private set; }
public byte[]? ManifestPayload { get; private set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri!.AbsoluteUri.EndsWith("/api/offline-kit/import", StringComparison.OrdinalIgnoreCase))
{
Assert.IsType<MultipartFormDataContent>(request.Content);
foreach (var part in (MultipartFormDataContent)request.Content!)
{
var name = part.Headers.ContentDisposition?.Name?.Trim('"');
switch (name)
{
case "metadata":
MetadataJson = await part.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
break;
case "bundle":
BundlePayload = await part.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
break;
case "manifest":
ManifestPayload = await part.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
break;
}
}
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("{\"importId\":\"imp-1\",\"status\":\"queued\",\"submittedAt\":\"2025-10-21T00:00:00Z\"}")
};
}
}
private sealed class StubTokenClient : IStellaOpsTokenClient
{
private readonly StellaOpsTokenResult _tokenResult;