feat: Implement Runtime Facts ingestion service and NDJSON reader
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added RuntimeFactsNdjsonReader for reading NDJSON formatted runtime facts. - Introduced IRuntimeFactsIngestionService interface and its implementation. - Enhanced Program.cs to register new services and endpoints for runtime facts. - Updated CallgraphIngestionService to include CAS URI in stored artifacts. - Created RuntimeFactsValidationException for validation errors during ingestion. - Added tests for RuntimeFactsIngestionService and RuntimeFactsNdjsonReader. - Implemented SignalsSealedModeMonitor for compliance checks in sealed mode. - Updated project dependencies for testing utilities.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
using StellaOps.Scanner.Analyzers.Lang.Python;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Python;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Python.Tests;
|
||||
|
||||
@@ -80,4 +83,126 @@ public sealed class PythonLanguageAnalyzerTests
|
||||
cancellationToken,
|
||||
usageHints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockfileCollectorEmitsDeclaredOnlyComponentsAsync()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
var fixturePath = CreateTemporaryWorkspace();
|
||||
try
|
||||
{
|
||||
await CreatePythonPackageAsync(fixturePath, "locked", "1.0.0", cancellationToken);
|
||||
await CreatePythonPackageAsync(fixturePath, "runtime-only", "2.0.0", cancellationToken);
|
||||
|
||||
var requirementsPath = Path.Combine(fixturePath, "requirements.txt");
|
||||
var requirements = new StringBuilder()
|
||||
.AppendLine("locked==1.0.0")
|
||||
.AppendLine("declared-only==3.0.0")
|
||||
.ToString();
|
||||
await File.WriteAllTextAsync(requirementsPath, requirements, cancellationToken);
|
||||
|
||||
var analyzers = new ILanguageAnalyzer[]
|
||||
{
|
||||
new PythonLanguageAnalyzer()
|
||||
};
|
||||
|
||||
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
|
||||
fixturePath,
|
||||
analyzers,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var document = JsonDocument.Parse(json);
|
||||
var root = document.RootElement;
|
||||
|
||||
Assert.True(ComponentHasMetadata(root, "declared-only", "declaredOnly", "true"));
|
||||
Assert.True(ComponentHasMetadata(root, "declared-only", "lockSource", "requirements.txt"));
|
||||
Assert.True(ComponentHasMetadata(root, "locked", "lockSource", "requirements.txt"));
|
||||
Assert.True(ComponentHasMetadata(root, "runtime-only", "lockMissing", "true"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(fixturePath, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task CreatePythonPackageAsync(string root, string name, string version, CancellationToken cancellationToken)
|
||||
{
|
||||
var sitePackages = Path.Combine(root, "lib", "python3.11", "site-packages");
|
||||
Directory.CreateDirectory(sitePackages);
|
||||
|
||||
var packageDir = Path.Combine(sitePackages, name);
|
||||
Directory.CreateDirectory(packageDir);
|
||||
|
||||
var modulePath = Path.Combine(packageDir, "__init__.py");
|
||||
var moduleContent = $"__version__ = \"{version}\"{Environment.NewLine}";
|
||||
await File.WriteAllTextAsync(modulePath, moduleContent, cancellationToken);
|
||||
|
||||
var distInfoDir = Path.Combine(sitePackages, $"{name}-{version}.dist-info");
|
||||
Directory.CreateDirectory(distInfoDir);
|
||||
|
||||
var metadataPath = Path.Combine(distInfoDir, "METADATA");
|
||||
var metadataContent = $"Metadata-Version: 2.1{Environment.NewLine}Name: {name}{Environment.NewLine}Version: {version}{Environment.NewLine}";
|
||||
await File.WriteAllTextAsync(metadataPath, metadataContent, cancellationToken);
|
||||
|
||||
var wheelPath = Path.Combine(distInfoDir, "WHEEL");
|
||||
await File.WriteAllTextAsync(wheelPath, "Wheel-Version: 1.0", cancellationToken);
|
||||
|
||||
var entryPointsPath = Path.Combine(distInfoDir, "entry_points.txt");
|
||||
await File.WriteAllTextAsync(entryPointsPath, string.Empty, cancellationToken);
|
||||
|
||||
var recordPath = Path.Combine(distInfoDir, "RECORD");
|
||||
var recordContent = new StringBuilder()
|
||||
.AppendLine($"{name}/__init__.py,sha256={ComputeSha256Base64(modulePath)},{new FileInfo(modulePath).Length}")
|
||||
.AppendLine($"{name}-{version}.dist-info/METADATA,sha256={ComputeSha256Base64(metadataPath)},{new FileInfo(metadataPath).Length}")
|
||||
.AppendLine($"{name}-{version}.dist-info/RECORD,,")
|
||||
.AppendLine($"{name}-{version}.dist-info/WHEEL,sha256={ComputeSha256Base64(wheelPath)},{new FileInfo(wheelPath).Length}")
|
||||
.AppendLine($"{name}-{version}.dist-info/entry_points.txt,sha256={ComputeSha256Base64(entryPointsPath)},{new FileInfo(entryPointsPath).Length}")
|
||||
.ToString();
|
||||
await File.WriteAllTextAsync(recordPath, recordContent, cancellationToken);
|
||||
}
|
||||
|
||||
private static bool ComponentHasMetadata(JsonElement root, string componentName, string key, string? expectedValue)
|
||||
{
|
||||
foreach (var component in root.EnumerateArray())
|
||||
{
|
||||
if (!component.TryGetProperty("name", out var nameElement) ||
|
||||
!string.Equals(nameElement.GetString(), componentName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component.TryGetProperty("metadata", out var metadataElement) || metadataElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!metadataElement.TryGetProperty(key, out var valueElement))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var actual = valueElement.GetString();
|
||||
if (string.Equals(actual, expectedValue, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string ComputeSha256Base64(string path)
|
||||
{
|
||||
using var sha = SHA256.Create();
|
||||
using var stream = File.OpenRead(path);
|
||||
var hash = sha.ComputeHash(stream);
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
private static string CreateTemporaryWorkspace()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"stellaops-python-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user