Add unit tests and implementations for MongoDB index models and OpenAPI metadata
- Implemented `MongoIndexModelTests` to verify index models for various stores. - Created `OpenApiMetadataFactory` with methods to generate OpenAPI metadata. - Added tests for `OpenApiMetadataFactory` to ensure expected defaults and URL overrides. - Introduced `ObserverSurfaceSecrets` and `WebhookSurfaceSecrets` for managing secrets. - Developed `RuntimeSurfaceFsClient` and `WebhookSurfaceFsClient` for manifest retrieval. - Added dependency injection tests for `SurfaceEnvironmentRegistration` in both Observer and Webhook contexts. - Implemented tests for secret resolution in `ObserverSurfaceSecretsTests` and `WebhookSurfaceSecretsTests`. - Created `EnsureLinkNotMergeCollectionsMigrationTests` to validate MongoDB migration logic. - Added project files for MongoDB tests and NuGet package mirroring.
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.DotNet.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves publish artifacts (deps/runtimeconfig) into deterministic entrypoint identities.
|
||||
/// </summary>
|
||||
public static class DotNetEntrypointResolver
|
||||
{
|
||||
private static readonly EnumerationOptions Enumeration = new()
|
||||
{
|
||||
RecurseSubdirectories = true,
|
||||
IgnoreInaccessible = true,
|
||||
AttributesToSkip = FileAttributes.Device | FileAttributes.ReparsePoint
|
||||
};
|
||||
|
||||
public static ValueTask<IReadOnlyList<DotNetEntrypoint>> ResolveAsync(
|
||||
LanguageAnalyzerContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var depsFiles = Directory
|
||||
.EnumerateFiles(context.RootPath, "*.deps.json", Enumeration)
|
||||
.OrderBy(static path => path, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
if (depsFiles.Length == 0)
|
||||
{
|
||||
return ValueTask.FromResult<IReadOnlyList<DotNetEntrypoint>>(Array.Empty<DotNetEntrypoint>());
|
||||
}
|
||||
|
||||
var results = new List<DotNetEntrypoint>(depsFiles.Length);
|
||||
|
||||
foreach (var depsPath in depsFiles)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var relativeDepsPath = NormalizeRelative(context.GetRelativePath(depsPath));
|
||||
var depsFile = DotNetDepsFile.Load(depsPath, relativeDepsPath, cancellationToken);
|
||||
if (depsFile is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DotNetRuntimeConfig? runtimeConfig = null;
|
||||
var runtimeConfigPath = Path.ChangeExtension(depsPath, ".runtimeconfig.json");
|
||||
string? relativeRuntimeConfig = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(runtimeConfigPath) && File.Exists(runtimeConfigPath))
|
||||
{
|
||||
relativeRuntimeConfig = NormalizeRelative(context.GetRelativePath(runtimeConfigPath));
|
||||
runtimeConfig = DotNetRuntimeConfig.Load(runtimeConfigPath, relativeRuntimeConfig, cancellationToken);
|
||||
}
|
||||
|
||||
var tfms = CollectTargetFrameworks(depsFile, runtimeConfig);
|
||||
var rids = CollectRuntimeIdentifiers(depsFile, runtimeConfig);
|
||||
var publishKind = DeterminePublishKind(depsFile);
|
||||
|
||||
var name = GetEntrypointName(depsPath);
|
||||
var id = BuildDeterministicId(name, tfms, rids, publishKind);
|
||||
|
||||
results.Add(new DotNetEntrypoint(
|
||||
Id: id,
|
||||
Name: name,
|
||||
TargetFrameworks: tfms,
|
||||
RuntimeIdentifiers: rids,
|
||||
RelativeDepsPath: relativeDepsPath,
|
||||
RelativeRuntimeConfigPath: relativeRuntimeConfig,
|
||||
PublishKind: publishKind));
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return ValueTask.FromResult<IReadOnlyList<DotNetEntrypoint>>(results);
|
||||
}
|
||||
|
||||
private static string GetEntrypointName(string depsPath)
|
||||
{
|
||||
// Strip .json then any trailing .deps suffix to yield a logical entrypoint name.
|
||||
var stem = Path.GetFileNameWithoutExtension(depsPath); // removes .json
|
||||
|
||||
if (stem.EndsWith(".deps", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stem = stem[..^".deps".Length];
|
||||
}
|
||||
|
||||
return stem;
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<string> CollectTargetFrameworks(DotNetDepsFile depsFile, DotNetRuntimeConfig? runtimeConfig)
|
||||
{
|
||||
var tfms = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var library in depsFile.Libraries.Values)
|
||||
{
|
||||
foreach (var tfm in library.TargetFrameworks)
|
||||
{
|
||||
tfms.Add(tfm);
|
||||
}
|
||||
}
|
||||
|
||||
if (runtimeConfig is not null)
|
||||
{
|
||||
foreach (var tfm in runtimeConfig.Tfms)
|
||||
{
|
||||
tfms.Add(tfm);
|
||||
}
|
||||
|
||||
foreach (var framework in runtimeConfig.Frameworks)
|
||||
{
|
||||
tfms.Add(framework);
|
||||
}
|
||||
}
|
||||
|
||||
return tfms;
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<string> CollectRuntimeIdentifiers(DotNetDepsFile depsFile, DotNetRuntimeConfig? runtimeConfig)
|
||||
{
|
||||
var rids = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var library in depsFile.Libraries.Values)
|
||||
{
|
||||
foreach (var rid in library.RuntimeIdentifiers)
|
||||
{
|
||||
rids.Add(rid);
|
||||
}
|
||||
}
|
||||
|
||||
if (runtimeConfig is not null)
|
||||
{
|
||||
foreach (var entry in runtimeConfig.RuntimeGraph)
|
||||
{
|
||||
rids.Add(entry.Rid);
|
||||
|
||||
foreach (var fallback in entry.Fallbacks)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(fallback))
|
||||
{
|
||||
rids.Add(fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rids;
|
||||
}
|
||||
|
||||
private static DotNetPublishKind DeterminePublishKind(DotNetDepsFile depsFile)
|
||||
{
|
||||
foreach (var library in depsFile.Libraries.Values)
|
||||
{
|
||||
if (library.Id.StartsWith("Microsoft.NETCore.App.Runtime.", StringComparison.OrdinalIgnoreCase) ||
|
||||
library.Id.StartsWith("Microsoft.WindowsDesktop.App.Runtime.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DotNetPublishKind.SelfContained;
|
||||
}
|
||||
}
|
||||
|
||||
return DotNetPublishKind.FrameworkDependent;
|
||||
}
|
||||
|
||||
private static string BuildDeterministicId(
|
||||
string name,
|
||||
IReadOnlyCollection<string> tfms,
|
||||
IReadOnlyCollection<string> rids,
|
||||
DotNetPublishKind publishKind)
|
||||
{
|
||||
var tfmPart = tfms.Count == 0 ? "unknown" : string.Join('+', tfms.OrderBy(t => t, StringComparer.OrdinalIgnoreCase));
|
||||
var ridPart = rids.Count == 0 ? "none" : string.Join('+', rids.OrderBy(r => r, StringComparer.OrdinalIgnoreCase));
|
||||
var publishPart = publishKind.ToString().ToLowerInvariant();
|
||||
return $"{name}:{tfmPart}:{ridPart}:{publishPart}";
|
||||
}
|
||||
|
||||
private static string NormalizeRelative(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || path == ".")
|
||||
{
|
||||
return ".";
|
||||
}
|
||||
|
||||
var normalized = path.Replace('\\', '/');
|
||||
return string.IsNullOrWhiteSpace(normalized) ? "." : normalized;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record DotNetEntrypoint(
|
||||
string Id,
|
||||
string Name,
|
||||
IReadOnlyCollection<string> TargetFrameworks,
|
||||
IReadOnlyCollection<string> RuntimeIdentifiers,
|
||||
string RelativeDepsPath,
|
||||
string? RelativeRuntimeConfigPath,
|
||||
DotNetPublishKind PublishKind);
|
||||
|
||||
public enum DotNetPublishKind
|
||||
{
|
||||
Unknown = 0,
|
||||
FrameworkDependent = 1,
|
||||
SelfContained = 2
|
||||
}
|
||||
Reference in New Issue
Block a user