feat: Add tests for RichGraphPublisher and RichGraphWriter
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
- Implement unit tests for RichGraphPublisher to verify graph publishing to CAS. - Implement unit tests for RichGraphWriter to ensure correct writing of canonical graphs and metadata. feat: Implement AOC Guard validation logic - Add AOC Guard validation logic to enforce document structure and field constraints. - Introduce violation codes for various validation errors. - Implement tests for AOC Guard to validate expected behavior. feat: Create Console Status API client and service - Implement ConsoleStatusClient for fetching console status and streaming run events. - Create ConsoleStatusService to manage console status polling and event subscriptions. - Add tests for ConsoleStatusClient to verify API interactions. feat: Develop Console Status component - Create ConsoleStatusComponent for displaying console status and run events. - Implement UI for showing status metrics and handling user interactions. - Add styles for console status display. test: Add tests for Console Status store - Implement tests for ConsoleStatusStore to verify event handling and state management.
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability;
|
||||
|
||||
/// <summary>
|
||||
/// Writes richgraph-v1 documents to disk with canonical ordering and BLAKE3 hash.
|
||||
/// </summary>
|
||||
public sealed class RichGraphWriter
|
||||
{
|
||||
private static readonly JsonWriterOptions JsonOptions = new()
|
||||
{
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Indented = false,
|
||||
SkipValidation = false
|
||||
};
|
||||
|
||||
public async Task<RichGraphWriteResult> WriteAsync(
|
||||
RichGraph graph,
|
||||
string outputRoot,
|
||||
string analysisId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(graph);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(outputRoot);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(analysisId);
|
||||
|
||||
var trimmed = graph.Trimmed();
|
||||
var root = Path.Combine(outputRoot, "reachability_graphs", analysisId);
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
var graphPath = Path.Combine(root, "richgraph-v1.json");
|
||||
|
||||
await using (var stream = File.Create(graphPath))
|
||||
await using (var writer = new Utf8JsonWriter(stream, JsonOptions))
|
||||
{
|
||||
WriteGraph(writer, trimmed);
|
||||
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var bytes = await File.ReadAllBytesAsync(graphPath, cancellationToken).ConfigureAwait(false);
|
||||
var graphHash = ComputeSha256(bytes);
|
||||
|
||||
var metaPath = Path.Combine(root, "meta.json");
|
||||
await using (var stream = File.Create(metaPath))
|
||||
await using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("schema", trimmed.Schema);
|
||||
writer.WriteString("graph_hash", graphHash);
|
||||
writer.WritePropertyName("files");
|
||||
writer.WriteStartArray();
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("path", graphPath);
|
||||
writer.WriteString("hash", graphHash);
|
||||
writer.WriteEndObject();
|
||||
writer.WriteEndArray();
|
||||
writer.WriteEndObject();
|
||||
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new RichGraphWriteResult(graphPath, metaPath, graphHash, trimmed.Nodes.Count, trimmed.Edges.Count);
|
||||
}
|
||||
|
||||
private static void WriteGraph(Utf8JsonWriter writer, RichGraph graph)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("schema", graph.Schema);
|
||||
|
||||
writer.WritePropertyName("analyzer");
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("name", graph.Analyzer.Name);
|
||||
writer.WriteString("version", graph.Analyzer.Version);
|
||||
if (!string.IsNullOrWhiteSpace(graph.Analyzer.ToolchainDigest))
|
||||
{
|
||||
writer.WriteString("toolchain_digest", graph.Analyzer.ToolchainDigest);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
|
||||
writer.WritePropertyName("nodes");
|
||||
writer.WriteStartArray();
|
||||
foreach (var node in graph.Nodes)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("id", node.Id);
|
||||
writer.WriteString("symbol_id", node.SymbolId);
|
||||
writer.WriteString("lang", node.Lang);
|
||||
writer.WriteString("kind", node.Kind);
|
||||
if (!string.IsNullOrWhiteSpace(node.Display)) writer.WriteString("display", node.Display);
|
||||
if (!string.IsNullOrWhiteSpace(node.CodeId)) writer.WriteString("code_id", node.CodeId);
|
||||
if (!string.IsNullOrWhiteSpace(node.Purl)) writer.WriteString("purl", node.Purl);
|
||||
if (!string.IsNullOrWhiteSpace(node.BuildId)) writer.WriteString("build_id", node.BuildId);
|
||||
if (!string.IsNullOrWhiteSpace(node.SymbolDigest)) writer.WriteString("symbol_digest", node.SymbolDigest);
|
||||
|
||||
if (node.Evidence is { Count: > 0 })
|
||||
{
|
||||
writer.WritePropertyName("evidence");
|
||||
writer.WriteStartArray();
|
||||
foreach (var e in node.Evidence) writer.WriteStringValue(e);
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (node.Attributes is { Count: > 0 })
|
||||
{
|
||||
writer.WritePropertyName("attributes");
|
||||
writer.WriteStartObject();
|
||||
foreach (var kv in node.Attributes)
|
||||
{
|
||||
writer.WriteString(kv.Key, kv.Value);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WritePropertyName("edges");
|
||||
writer.WriteStartArray();
|
||||
foreach (var edge in graph.Edges)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("from", edge.From);
|
||||
writer.WriteString("to", edge.To);
|
||||
writer.WriteString("kind", edge.Kind);
|
||||
if (!string.IsNullOrWhiteSpace(edge.Purl)) writer.WriteString("purl", edge.Purl);
|
||||
if (!string.IsNullOrWhiteSpace(edge.SymbolDigest)) writer.WriteString("symbol_digest", edge.SymbolDigest);
|
||||
writer.WriteNumber("confidence", edge.Confidence);
|
||||
|
||||
if (edge.Evidence is { Count: > 0 })
|
||||
{
|
||||
writer.WritePropertyName("evidence");
|
||||
writer.WriteStartArray();
|
||||
foreach (var e in edge.Evidence) writer.WriteStringValue(e);
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
if (edge.Candidates is { Count: > 0 })
|
||||
{
|
||||
writer.WritePropertyName("candidates");
|
||||
writer.WriteStartArray();
|
||||
foreach (var c in edge.Candidates) writer.WriteStringValue(c);
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WritePropertyName("roots");
|
||||
writer.WriteStartArray();
|
||||
foreach (var root in graph.Roots)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("id", root.Id);
|
||||
writer.WriteString("phase", root.Phase);
|
||||
if (!string.IsNullOrWhiteSpace(root.Source)) writer.WriteString("source", root.Source);
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private static string ComputeSha256(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(bytes.ToArray());
|
||||
return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record RichGraphWriteResult(
|
||||
string GraphPath,
|
||||
string MetaPath,
|
||||
string GraphHash,
|
||||
int NodeCount,
|
||||
int EdgeCount);
|
||||
Reference in New Issue
Block a user